Skip to content

ghc -M fails to correctly account for indirect SOURCE imports

Consider a program such as:

--- in the unit1 unit
-- Foo.hs-boot
module Foo where
foo :: String

-- Foo.hs
module Foo where
foo :: String
foo = "hi"

-- ... additional cyclic bindings here which make `Foo.hs-boot` necessary

-- Bar.hs 
module Bar where
import {-# SOURCE #-} Foo
bar = ... foo ...

--- in the unit2 unit
-- Baz.hs
module Baz where
import Bar
baz = ... bar ...

Imagine running ghc -M on Baz.hs. It will contain something like the following:

Baz.o : Baz.hs Bar.o
Bar.o : Bar.hs Foo.o-boot
Foo.o : Foo.hs
Foo.o-boot : Foo.hs-boot

This seems perfectly reasonable: it captures the dependency structure of the user's program.

However, if we consider what happens during one-shot compilation, we will see that it isn't quite right. Specifically, imagine that baz get an unfolding, which will naturally contain a reference to Bar.bar (which was SOURCE imported). When we compile Baz GHC will see that:

  1. we need a declaration for Foo.foo in order to unfold Bar.bar
  2. the user hasn't imported Bar directly; this means that GHC.Iface.Load.importDecl will call loadInterface with ImportBySystem
  3. GHC.Iface.Load.loadInterface calls GHC.Iface.Load.wantHiBootFile to determine what it should load
  4. wantHiBootFile sees that Foo is not in the unit currently being compiled. Consequently, it responds with NotBoot
  5. GHC attempts to load Foo.hi instead of Foo.hi-boot as the ghc -M output claims
Edited by Sebastian Graf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information