Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imports long list align + list align behaviour regression when set to newline #462

Open
paolino opened this issue Jun 26, 2023 · 1 comment

Comments

@paolino
Copy link

paolino commented Jun 26, 2023

In version 0.11.0.3, a configuration containing

     list_align: new_line
     long_list_align: new_line_multiline

was producing a nice consistent layout with all imported stuff in a new line and longer than 80 import lines broken in multiple lines

i.e. this test would have passed

case26b :: Assertion
case26b =
    assertSnippet (step (Just 80) options)
    [ "import Data.Map (Map)"
    , "import Data.List (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail, (++))"
    ]
    [ "import Data.List"
    , "    ( concat"
    , "    , foldl"
    , "    , foldr"
    , "    , head"
    , "    , init"
    , "    , last"
    , "    , length"
    , "    , map"
    , "    , null"
    , "    , reverse"
    , "    , tail"
    , "    , (++)"
    , "    )"
    , "import Data.Map"
    , "    (Map)"
    ]
  where
    options = defaultOptions { listAlign = NewLine, importAlign = None, longListAlign = Multiline}

But now, on 0.14.5.0, I cannot reproduce it. In fact, I get

expected: 
import Data.List
    ( concat
    , foldl
    , foldr
    , head
    , init
    , last
    , length
    , map
    , null
    , reverse
    , tail
    , (++)
    )
import Data.Map
    (Map)

 but got: 
import Data.List
    ( concat
    , foldl
    , foldr
    , head
    , init
    , last
    , length
    , map
    , null
    , reverse
    , tail
    , (++)
    )
import Data.Map (Map)

where the (Map) is not on a new line.

also, without the longListAlign , the listAlign option is respected

i.e. this test passes

case26a :: Assertion
case26a =
    assertSnippet (step (Just 80) options)
    [ "import Data.Map (Map)"
    , "import Data.List (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail, (++))"
    ]
    [ "import Data.List"
    , "    (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail,"
    , "    (++))"
    , "import Data.Map"
    , "    (Map)"
    ]
  where
    options = defaultOptions { listAlign = NewLine, importAlign = None}

Maybe I need a different combination of the options? Any help appreciated

@paolino paolino changed the title imports newmultiline + newline behaviour regression imports long list align + list align behaviour regression when set to newline Jun 26, 2023
github-merge-queue bot pushed a commit to cardano-foundation/cardano-wallet that referenced this issue Oct 20, 2023
## Summary

This PR adjusts our `stylish-haskell` configuration to always import
each symbol on a separate line. Example:
```hs
import Some.Module.A
    ( SomeSymbolA1
    , SomeSymbolA2
    )
import Some.Module.B
    ( SomeSymbolB1
    , SomeSymbolB2
    , SomeSymbolB3
    , SomeSymbolB4
    )
````

## Details

This change has several advantages:
1. **Better cross-version compatibility**: this configuration produces
[almost exactly the
same](731127c)
output across versions `0.11.0.3` and `0.14.5.0` of `stylish-haskell`
(current and latest versions respectively).
1. **Better cross-tool compatibility**: the output is almost identical
to that produced by **`fourmolu`**. (See experimental `fourmolu` branch
[here](https://github.com/cardano-foundation/cardano-wallet/pull/4061/files).)
1. **Improved diff-friendliness**: we virtually eliminate the problem
where adding or removing a single import can lead to a multi-line diff.

Adopting this style will make it easier to move to GHC `9.2` or `9.6`,
both of which no longer work with our current version of
`stylish-haskell`.

## Related Issues

Without this change in our configuration, upgrading `stylish-haskell`
from version `0.11.0.3` to `0.14.5.0` exposes us to the following
regression:
haskell/stylish-haskell#462
sorki added a commit to sorki/stylish-haskell that referenced this issue Nov 7, 2023
The documentation of these doesn't quite reflect the implementation
and it seems to cause some confusion.

This patch tries to address it by renaming `LongListAlign`
constructors to better reflect what they do and fix their
documentation in `stylish-haskell.yaml`.

There is no change in functionality but it introduces a breaking
change for users using the Haskell interface. It also adds
a (lousy) concept of config option deprecation in form of documenting
the deprecated variants in favor of new names.

Related to haskell#462
@sorki
Copy link

sorki commented Nov 7, 2023

This is a fun one!

In version 0.11.0.3, a configuration containing

     list_align: new_line
     long_list_align: new_line_multiline

was producing a nice consistent layout with all imported stuff in a new line and longer than 80 import lines broken in multiple lines

Note that new_line_multiline corresponds to InlineToMultiline of LongListAlign which actually means try inline first, if too long go multiline.

i.e. this test would have passed

case26b :: Assertion
case26b =
    assertSnippet (step (Just 80) options)
    [ "import Data.Map (Map)"
    , "import Data.List (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail, (++))"
    ]
    [ "import Data.List"
    , "    ( concat"
    , "    , foldl"
    , "    , foldr"
    , "    , head"
    , "    , init"
    , "    , last"
    , "    , length"
    , "    , map"
    , "    , null"
    , "    , reverse"
    , "    , tail"
    , "    , (++)"
    , "    )"
    , "import Data.Map"
    , "    (Map)"
    ]
  where
    options = defaultOptions { listAlign = NewLine, importAlign = None, longListAlign = Multiline}

Multiline vs InlineToMultiline.

The PR #467 tries to address this by introducing better names for constructors and their respective parser options.

But now, on 0.14.5.0, I cannot reproduce it. In fact, I get

expected: 
import Data.List
    ( concat
    , foldl
    , foldr
    , head
    , init
    , last
    , length
    , map
    , null
    , reverse
    , tail
    , (++)
    )
import Data.Map
    (Map)

 but got: 
import Data.List
    ( concat
    , foldl
    , foldr
    , head
    , init
    , last
    , length
    , map
    , null
    , reverse
    , tail
    , (++)
    )
import Data.Map (Map)

where the (Map) is not on a new line.

Which is because InlineToMultiline first tries inline mode, and import Data.Map (Map) fits.

also, without the longListAlign , the listAlign option is respected

i.e. this test passes

case26a :: Assertion
case26a =
    assertSnippet (step (Just 80) options)
    [ "import Data.Map (Map)"
    , "import Data.List (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail, (++))"
    ]
    [ "import Data.List"
    , "    (concat, foldl, foldr, head, init, last, length, map, null, reverse, tail,"
    , "    (++))"
    , "import Data.Map"
    , "    (Map)"
    ]
  where
    options = defaultOptions { listAlign = NewLine, importAlign = None}

This is because the default LongListAlign (Inline) has a special case

Inline | NewLine <- listAlign -> do
modifyCurrentLine trimRight
newline >> putOffset >> printAsInlineWrapping (putText wrapPrefix)

But it is also the only LongListAlign variant that has it.

Maybe I need a different combination of the options? Any help appreciated

I'm not exactly sure, what is the right solution here

import Data.Map
    ( Map
    )

so it almost seems that there should be a LongListAlign Newline mode so this is explicit (but it would clash with new_line parser value at the moment - #467) or ListAlign needs to be respected in other than just the default Inline of LongListAlign.

Another possibility might be to deprecate ListAlign completely in favor of LongListAlign (which seems to be the main type governing the output) and rename LongListAlign to ListAlign but this seems quite difficult to achieve in backwards compatible manner.

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

No branches or pull requests

2 participants