haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.63k stars 697 forks source link

CPP macros suitable for partial evaluation #9329

Open andreasabel opened 1 year ago

andreasabel commented 1 year ago

I looked into tools that could simplify my codebase by partially evaluating CPP conditionals. E.g. if I have

#if __GLASGOW_HASKELL__ < 806
...
#if !MIN_VERSION_base(4,11,0)
...

and I no longer support GHC < 8.6 or base < 4.11 then I can get rid of some cruft by partially evaluating the conditions and throw away the conditional together with the branch that is now impossible..

I checked some tools that do some partial evaluation of CPP conditionals:

These tools typically only support setting a symbol to a value, like __GLASGOW_HASKELL__=806. They do not accept predicates, i.e., I cannot set MIN_VERSION_base(4,11,0) to be true.

So if Cabal defined 3 separate symbols to specify a version, instead of a ternary predicate, this would be compatible with such tools.

MAJOR1_VERSION_base=4
MAJOR2_VERSION_base=11
MINOR_VERSION_base=0

This would be usable in Haskell code as such:

#if MAJOR1_VERSION_base >= 4 && MAJOR2_VERSION_base >= 11
...

(One could think of other macro names, like the triple VERSION_EDITION_base, VERSION_MAJOR_base, VERSION_MINOR_base---although the terminology edition for the "major-major" version is not established.)

phadej commented 1 year ago

Few comments, it's also GHC which defines MIN_VERSION_base macros, e.g. given Ex.hs

{-# LANGUAGE CPP #-}

main :: IO ()
#if MIN_VERSION_base(4,19,0)
main = print True
#else
main = print False
#endif

we have

% runghc-9.8.1 Ex.hs 
True
% runghc-9.6.2 Ex.hs
False

I don't know (or remember) why GHC defines them. Would be get to find out, and check if you'd also like/need to change GHC.


I'd prefer

#define SUPER_VERSION_base 4
#define MAJOR_VERSION_base 19
#define MINOR_VERSION_base 0
#define PATCH_VERSION_base 0

just because I likes justified identifiers :P


I'd appreciate if changes to cabal_macros.h generation were explicitly part of the .cabal format spec, i.e. to get new functionality, you'd need to use more recent cabal-version.

phadej commented 1 year ago

And not having new features of cabal_macros.h guarded behind cabal-version: ... will result into issues like https://github.com/haskell/cabal/issues/9331

This is a reason why I think that cabal_macros.h, Paths_pgkname.hs generating code SHOULD be in Cabal-syntax, as it's the package defining (most of) the file format.

Specifically, if something in Cabal-syntax changes (say, new field) then there probably should be a new cabal-version, and perfectly if we won't need to introduce new cabal-version: (CabalVersionSpec constructors) if nothing changes in Cabal-syntax. The latter is exactly what happened leading to #9331: No change in Cabal-syntax, so no-one thought about having new cabal-version nor cabal-version checks.

michaelpj commented 1 year ago

I was able to make this work by:

  1. Replacing predicate calls by constant macros.
  2. Using coan (I tried unifdef but it doesn't simplify conditional expressions)

In the end I did something like this to remove all MIN_VERSION_ghc(9,2,*) macros:

rg 'MIN_VERSION_ghc' -l | xargs -I {} bash -c "sed -i -e 's/MIN_VERSION_ghc(9,2,[0-9]*)/ALWAYS_TRUE/g' {} && coan source -r -DALWAYS_TRUE=1 {}"
michaelpj commented 1 year ago

I couldn't figure out a nice way to deal with __GLASGOW_HASKELL__ < 902, though. Setting it to a particular value would also replace all other __GLASGOW_HASKELL__ comparisons.

michaelpj commented 1 year ago

The same trick with replacing particular comparisons with a constant would work, though, if the form is consistent enough in your codebase.