haskell / haddock

Haskell Documentation Tool
www.haskell.org/haddock/
BSD 2-Clause "Simplified" License
361 stars 241 forks source link

Type signature of raise# is wrong #838

Open andrewthad opened 6 years ago

andrewthad commented 6 years ago

The original discussion of this issue is on trac issue 15181. At GHCi, we can inspect the type of GHC.Exts.raise#:

>>> import GHC.Exts
>>> :set -XMagicHash
>>> :info raise#
raise# :: forall b (q :: RuntimeRep) (a :: TYPE q). b -> a
        -- Defined in ‘GHC.Prim’

However, the type of raise# given in the haddocks is missing the levity polymorphic kinds:

raise# :: a -> o
RyanGlScott commented 6 years ago

This is not simple to fix. The problem is that raise# is defined in GHC.Prim, which involves a fair amount of magic. In particular, GHC wires in extra typing information for a good number of functions defined in GHC.Prim, which means that even though the type signature that Haddock sees for raise# is a -> o (as evidenced here), in reality, GHC sneaks in some extra levity polymorphism under the hood.

I don't know a simple way to work around this.

harpocrates commented 6 years ago

Note: this is fixed in the Hi Haddock branch:

screen shot 2018-09-16 at 5 04 55 pm
RyanGlScott commented 6 years ago

Wow! Out of curiosity, how does Hi Haddock figure out what the correct type for raise# is?

harpocrates commented 6 years ago

Wow! Out of curiosity, how does Hi Haddock figure out what the correct type for raise# is?

That's the type that GHC uses internally, no? Hi Haddock gets its types by querying GHC (instead of by looking at surface syntax in the initial source file, as today's Haddock does). Even TH can figure out the right type for raise#:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Ppr
import GHC.Prim

main = putStrLn $(qReify (mkName "raise#") >>= lift . pprint)
-- GHC.Prim.raise# :: forall (b_0 :: *) (q_1 :: GHC.Types.RuntimeRep) (a_2 :: GHC.Prim.TYPE q_1) .
--                    b_0 -> a_2

I think the story for Haddock getting an incorrect type today is more interesting. Judging from https://ghc.haskell.org/trac/ghc/wiki/Commentary/PrimOps, looks like today's Haddock decides on the types of things in GHC.Prim by getting tricked into parsing an auto-generated file. In that file, one finds nonsense like:

raise# :: b -> o
raise# = raise#

This makes me think:

harpocrates commented 6 years ago

Also, I'm not sure how I feel levity polymorphism now showing up in the prelude:

screen shot 2018-09-16 at 5 51 17 pm

On one hand, I like how honest these types are. But are they really what we want beginners to see?

RyanGlScott commented 6 years ago

Hi Haddock gets its types by querying GHC (instead of by looking at surface syntax in the initial source file, as today's Haddock does).

That's wonderful! I've always been slightly annoyed by the fact that Haddock didn't do this before, so this addresses one of its major shortcomings (IMO). Great work.

we should probably stop generating that file once Hi Haddock is merged (or at least update the commentary/comments indicating that the generated file is not used at all)

That would be an interesting idea. According to the comment at the top of GHC.Prim:

{-
This is a generated file (generated by genprimopcode).
It is not code to actually be used. Its only purpose is to be
consumed by haddock.
-}

I wonder what would happen if we did remove GHC.Prim entirely. Obviously, several libraries would break, since they (foolishly) import from GHC.Prim directly. But assuming they all migrated to importing GHC.Exts instead, would anything else break? I can't say with 100% that the answer to that question is "no", but I'm optimistic.

Also, I'm not sure how I feel levity polymorphism now showing up in the prelude:

Yes, now that Haddock is honest about type signatures, I suppose we have to address this problem too. GHC handles this issue with the -fprint-explicit-runtime-reps flag (off by default), which controls whether the defaultRuntimeRepVars function is called or not. defaultRuntimeRepVars works over IfaceTypes, but you may find a similar trick useful for Haddock's purposes as well.

andrewthad commented 6 years ago

Wow, the hi haddock stuff looks really neat. I share your concerns about the levity-polymorphic type signature for $. To my understanding, the implementers of levity polymorphism worked hard to make sure that things like :t ($) in ghci do not show the levity polymorphic signature. I feel that haddock should probably take a similar approach.

harpocrates commented 6 years ago

I wonder what would happen if we did remove GHC.Prim entirely. Obviously, several libraries would break, since they (foolishly) import from GHC.Prim directly. But assuming they all migrated to importing GHC.Exts instead, would anything else break? I can't say with 100% that the answer to that question is "no", but I'm optimistic.

Doesn't GHC do something special when it sees GHC.Prim being imported? I'm almost certain it doesn't consult the generated file (since then programs would be working off of bugs types like raise# :: b -> o).

Yes, now that Haddock is honest about type signatures, I suppose we have to address this problem too. GHC handles this issue with the -fprint-explicit-runtime-reps flag (off by default), which controls whether the defaultRuntimeRepVars function is called or not. defaultRuntimeRepVars works over IfaceTypes, but you may find a similar trick useful for Haddock's purposes as well.

Perfect! I'll take a look at this. I suspect I'll end up porting this logic into the whole synifyType pipeline.

harpocrates commented 6 years ago

For the sake of posterity, I'd like to amend something I said earlier:

This makes me think:

  • we should probably stop generating that file once Hi Haddock is merged (or at least update the commentary/comments indicating that the generated file is not used at all)
    • if someone really wants to fix this bug before Hi Haddock gets merged, it should be doable via some monkeying about in genprimopcode

While it is true that the Hi Haddock work will mean that Haddock is no longer going to be using directly the type signatures of the things in the generated Haskell source for GHC.Prim (it instead queries GHC directly for type signatures of things), it does not mean that this generated source is no longer used.

The generated source has been, and continues to be, a vessel for:

If we ever do want to avoid generating this Haskell source, we'd probably want to have ghcPrimIface :: ModIface be fully formed (ie. have docstrings and whatever other Haddock stuff). That would likely involve adding more functions like primOpFixity, allThePrimOps, and so on - except this time for getting things like docstrings.