Skip to content

New plugin: Explicit record fields #3304

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

Merged
merged 37 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bc3948e
Initial working version
ozkutuk Oct 11, 2022
ff6c121
Auto-add puns pragma, fix behavior with Hs98 fields
ozkutuk Oct 11, 2022
3333977
Patch pragma so it adds NamedFieldPuns instead of RecordPuns
ozkutuk Oct 11, 2022
2657190
Refactor big do block
ozkutuk Oct 14, 2022
ef1fab4
Make it work with record construction
ozkutuk Oct 16, 2022
d0b37fe
Convert to a rule based approach
ozkutuk Oct 16, 2022
475737f
Cleanup, remove dead code
ozkutuk Oct 16, 2022
e418ac4
Make it compile with all supported GHC versions
ozkutuk Oct 17, 2022
3060535
Add tests
ozkutuk Oct 21, 2022
f8e2549
Minor code reorganization
ozkutuk Oct 21, 2022
2979131
Move common pragma logic to same file
ozkutuk Oct 25, 2022
b93422a
Remove strictness annotations
ozkutuk Oct 25, 2022
d213705
Improve documentation
ozkutuk Oct 25, 2022
89619b5
Use interval map for efficient range filtering
ozkutuk Oct 27, 2022
63167c8
Add external documentation
ozkutuk Oct 27, 2022
d378e6e
Add tests to GitHub CI
ozkutuk Oct 27, 2022
381cec5
Add debug log for collected records
ozkutuk Oct 28, 2022
50c6b73
Add `getExtensions` to ghcide
ozkutuk Oct 28, 2022
ac56006
Merge branch 'master' into explicit-record-fields
ozkutuk Oct 28, 2022
57646d3
Indicate that it doesn't work with GHC 9.4
ozkutuk Oct 28, 2022
972b578
Merge branch 'master' into explicit-record-fields
pepeiborra Nov 1, 2022
3a41f84
Merge branch 'master' into explicit-record-fields
ozkutuk Nov 1, 2022
7197db7
Relax version bounds on base
ozkutuk Nov 2, 2022
9ffd11d
Add plugin to stack packages
ozkutuk Nov 2, 2022
87b9acf
Add GHC 8.10 support
ozkutuk Nov 2, 2022
357f81c
Fix GHC 9.4 build failure
ozkutuk Nov 2, 2022
1acc61c
Make `conPatDetails` total
ozkutuk Nov 2, 2022
86a59df
Revert "Indicate that it doesn't work with GHC 9.4"
ozkutuk Nov 3, 2022
bb7afdd
Fix unused import caused by new compat exports
ozkutuk Nov 3, 2022
568710c
Merge branch 'master' into explicit-record-fields
ozkutuk Nov 3, 2022
987d950
Fix ConPat construction in GHC 8.10
ozkutuk Nov 7, 2022
74f950d
Merge branch 'master' into explicit-record-fields
ozkutuk Nov 8, 2022
aef23d3
Merge branch 'master' into explicit-record-fields
michaelpj Nov 8, 2022
3226577
Rename test-suite to make it shorter
ozkutuk Nov 9, 2022
45580ac
Merge branch 'master' into explicit-record-fields
michaelpj Nov 9, 2022
50ba2ef
Fix nix build by collecting latest hw-prim from Hackage
ozkutuk Nov 9, 2022
2934c5a
Merge branch 'master' into explicit-record-fields
ozkutuk Nov 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ jobs:
name: Test hls-explicit-fixity-plugin test suite
run: cabal test hls-explicit-fixity-plugin --test-options="$TEST_OPTS" || LSP_TEST_LOG_COLOR=0 LSP_TEST_LOG_MESSAGES=true LSP_TEST_LOG_STDERR=true cabal test hls-explicit-fixity-plugin --test-options="$TEST_OPTS"

- if: matrix.test
name: Test hls-explicit-record-fields-plugin test suite
run: cabal test hls-explicit-record-fields-plugin --test-options="$TEST_OPTS" || LSP_TEST_LOG_COLOR=0 LSP_TEST_LOG_MESSAGES=true LSP_TEST_LOG_STDERR=true cabal test hls-explicit-record-fields-plugin --test-options="$TEST_OPTS"

test_post_job:
if: always()
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ packages:
./plugins/hls-stan-plugin
./plugins/hls-gadt-plugin
./plugins/hls-explicit-fixity-plugin
./plugins/hls-explicit-record-fields-plugin
./plugins/hls-refactor-plugin

-- Standard location for temporary packages needed for particular environments
Expand All @@ -55,6 +56,8 @@ constraints:
entropy >= 0.4.1.10,
-- For GHC 9.4
basement >= 0.0.15,
-- For GHC 9.4
hw-prim >= 0.6.3.2,
hyphenation +embed,
-- remove this when hlint sets ghc-lib to true by default
-- https://github.com/ndmitchell/hlint/issues/1376
Expand Down
10 changes: 10 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,16 @@ Convert a datatype to GADT syntax.

![Link to Docs](../plugins/hls-gadt-plugin/README.md)

### Expand record wildcard

Provided by: `hls-explicit-record-fields-plugin`

Code action kind: `refactor.rewrite`

Expand record wildcards, explicitly listing all record fields as field puns.

![Explicit Wildcard Demo](../plugins/hls-explicit-record-fields-plugin/wildcard.gif)

## Code lenses

### Add type signature
Expand Down
1 change: 1 addition & 0 deletions docs/support/plugin-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ For example, a plugin to provide a formatter which has itself been abandoned has
| `hls-change-type-signature-plugin` | 2 | |
| `hls-eval-plugin` | 2 | 9.4 |
| `hls-explicit-fixity-plugin` | 2 | |
| `hls-explicit-record-fields-plugin` | 2 | |
| `hls-floskell-plugin` | 2 | 9.4 |
| `hls-fourmolu-plugin` | 2 | 9.4 |
| `hls-gadt-plugin` | 2 | 9.4 |
Expand Down
13 changes: 13 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
url = "https://hackage.haskell.org/package/hiedb-0.4.2.0/hiedb-0.4.2.0.tar.gz";
flake = false;
};
hw-prim = {
url = "https://hackage.haskell.org/package/hw-prim-0.6.3.2/hw-prim-0.6.3.2.tar.gz";
flake = false;
};
};
outputs =
inputs@{ self, nixpkgs, flake-compat, flake-utils, gitignore, all-cabal-hashes-unpacked, ... }:
Expand Down Expand Up @@ -182,6 +186,7 @@

entropy = hsuper.callCabal2nix "entropy" inputs.entropy {};
hiedb = hsuper.callCabal2nix "hiedb" inputs.hiedb {};
hw-prim = hsuper.callCabal2nix "hw-prim" inputs.hw-prim {};

implicit-hie-cradle = hself.callCabal2nix "implicit-hie-cradle" inputs.implicit-hie-cradle {};
ghc-check = hself.callCabal2nix "ghc-check" inputs.ghc-check {};
Expand Down
61 changes: 59 additions & 2 deletions ghcide/src/Development/IDE/GHC/Compat/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ module Development.IDE.GHC.Compat.Core (
),
pattern FunTy,
pattern ConPatIn,
conPatDetails,
mapConPatDetail,
#if !MIN_VERSION_ghc(9,2,0)
Development.IDE.GHC.Compat.Core.splitForAllTyCoVars,
#endif
Expand Down Expand Up @@ -253,6 +255,7 @@ module Development.IDE.GHC.Compat.Core (
SrcLoc.noSrcSpan,
SrcLoc.noSrcLoc,
SrcLoc.noLoc,
SrcLoc.mapLoc,
-- * Finder
FindResult(..),
mkHomeModLocation,
Expand Down Expand Up @@ -461,6 +464,18 @@ module Development.IDE.GHC.Compat.Core (
module GHC.Unit.Finder.Types,
module GHC.Unit.Env,
module GHC.Driver.Phases,
#endif
# if !MIN_VERSION_ghc(9,4,0)
pattern HsFieldBind,
hfbAnn,
hfbLHS,
hfbRHS,
hfbPun,
#endif
#if !MIN_VERSION_ghc_boot_th(9,4,1)
Extension(.., NamedFieldPuns),
#else
Extension(..)
#endif
) where

Expand Down Expand Up @@ -710,12 +725,12 @@ import TcRnMonad hiding (Applicative (..), IORef,
allM, anyM, concatMapM, foldrM,
mapMaybeM, (<$>))
import TcRnTypes
import TcType
import TcType
import qualified TcType
import TidyPgm as GHC
import qualified TyCoRep
import TyCon
import Type
import Type
import TysPrim
import TysWiredIn
import Unify
Expand Down Expand Up @@ -755,6 +770,11 @@ import qualified GHC.Driver.Finder as GHC
import qualified Finder as GHC
#endif

-- NOTE(ozkutuk): Cpp clashes Phase.Cpp, so we hide it.
-- Not the greatest solution, but gets the job done
-- (until the CPP extension is actually needed).
import GHC.LanguageExtensions.Type hiding (Cpp)


mkHomeModLocation :: DynFlags -> ModuleName -> FilePath -> IO Module.ModLocation
#if MIN_VERSION_ghc(9,3,0)
Expand Down Expand Up @@ -936,6 +956,25 @@ pattern ConPatIn con args = ConPat NoExtField con args
#endif
#endif

conPatDetails :: Pat p -> Maybe (HsConPatDetails p)
#if MIN_VERSION_ghc(9,0,0)
conPatDetails (ConPat _ _ args) = Just args
conPatDetails _ = Nothing
#else
conPatDetails (ConPatIn _ args) = Just args
conPatDetails _ = Nothing
#endif

mapConPatDetail :: (HsConPatDetails p -> Maybe (HsConPatDetails p)) -> Pat p -> Maybe (Pat p)
#if MIN_VERSION_ghc(9,0,0)
mapConPatDetail f pat@(ConPat _ _ args) = (\args' -> pat { pat_args = args'}) <$> f args
mapConPatDetail _ _ = Nothing
#else
mapConPatDetail f (ConPatIn ss args) = ConPatIn ss <$> f args
mapConPatDetail _ _ = Nothing
#endif


initDynLinker, initObjLinker :: HscEnv -> IO ()
initDynLinker =
#if !MIN_VERSION_ghc(9,0,0)
Expand Down Expand Up @@ -1101,3 +1140,21 @@ driverNoStop =
hscUpdateHPT :: (HomePackageTable -> HomePackageTable) -> HscEnv -> HscEnv
hscUpdateHPT k session = session { hsc_HPT = k (hsc_HPT session) }
#endif

#if !MIN_VERSION_ghc(9,2,0)
match :: HsRecField' id arg -> ((), id, arg, Bool)
match (HsRecField lhs rhs pun) = ((), SrcLoc.unLoc lhs, rhs, pun)

pattern HsFieldBind :: () -> id -> arg -> Bool -> HsRecField' id arg
pattern HsFieldBind {hfbAnn, hfbLHS, hfbRHS, hfbPun} <- (match -> (hfbAnn, hfbLHS, hfbRHS, hfbPun)) where
HsFieldBind _ lhs rhs pun = HsRecField (SrcLoc.noLoc lhs) rhs pun
#elif !MIN_VERSION_ghc(9,4,0)
pattern HsFieldBind :: XHsRecField id -> id -> arg -> Bool -> HsRecField' id arg
pattern HsFieldBind {hfbAnn, hfbLHS, hfbRHS, hfbPun} <- HsRecField hfbAnn (SrcLoc.unLoc -> hfbLHS) hfbRHS hfbPun where
HsFieldBind ann lhs rhs pun = HsRecField ann (SrcLoc.noLoc lhs) rhs pun
#endif

#if !MIN_VERSION_ghc_boot_th(9,4,1)
pattern NamedFieldPuns :: Extension
pattern NamedFieldPuns = RecordPuns
#endif
9 changes: 7 additions & 2 deletions ghcide/src/Development/IDE/GHC/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ module Development.IDE.GHC.Util(
setHieDir,
dontWriteHieFiles,
disableWarningsAsErrors,
printOutputable
printOutputable,
getExtensions
) where

#if MIN_VERSION_ghc(9,2,0)
import GHC.Data.EnumSet
import GHC.Data.FastString
import GHC.Data.StringBuffer
import GHC.Driver.Env hiding (hscSetFlags)
Expand Down Expand Up @@ -73,7 +75,7 @@ import Development.IDE.Types.Location
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Storable
import GHC
import GHC hiding (ParsedModule (..))
import GHC.IO.BufferedIO (BufferedIO)
import GHC.IO.Device as IODevice
import GHC.IO.Encoding
Expand Down Expand Up @@ -295,3 +297,6 @@ printOutputable =
-- More discussion at https://github.com/haskell/haskell-language-server/issues/3115.
unescape . T.pack . printWithoutUniques
{-# INLINE printOutputable #-}

getExtensions :: ParsedModule -> [Extension]
getExtensions = toList . extensionFlags . ms_hspp_opts . pm_mod_summary
31 changes: 26 additions & 5 deletions ghcide/src/Development/IDE/Spans/Pragmas.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ module Development.IDE.Spans.Pragmas
( NextPragmaInfo(..)
, LineSplitTextEdits(..)
, getNextPragmaInfo
, insertNewPragma ) where
, insertNewPragma
, getFirstPragma ) where

import Data.Bits (Bits (setBit))
import Data.Function ((&))
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import Data.Text (Text, pack)
import qualified Data.Text as Text
import Development.IDE (srcSpanToRange)
import Development.IDE (srcSpanToRange, IdeState, NormalizedFilePath, runAction, useWithStale, GhcSession (..), getFileContents, hscEnv)
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Compat.Util
import GHC.LanguageExtensions.Type (Extension)
import qualified Language.LSP.Types as LSP
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Except (ExceptT)
import Ide.Types (PluginId(..))
import qualified Data.Text as T
import Ide.PluginUtils (handleMaybeM)

getNextPragmaInfo :: DynFlags -> Maybe Text -> NextPragmaInfo
getNextPragmaInfo dynFlags sourceText =
Expand All @@ -31,13 +36,29 @@ getNextPragmaInfo dynFlags sourceText =
| otherwise
-> NextPragmaInfo 0 Nothing

-- NOTE(ozkutuk): `RecordPuns` extension is renamed to `NamedFieldPuns`
-- in GHC 9.4, but we still want to insert `NamedFieldPuns` in pre-9.4
-- GHC as well, hence the replacement.
-- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6156
showExtension :: Extension -> Text
showExtension NamedFieldPuns = "NamedFieldPuns"
showExtension ext = pack (show ext)

insertNewPragma :: NextPragmaInfo -> Extension -> LSP.TextEdit
insertNewPragma (NextPragmaInfo _ (Just (LineSplitTextEdits ins _))) newPragma = ins { LSP._newText = "{-# LANGUAGE " <> pack (show newPragma) <> " #-}\n" } :: LSP.TextEdit
insertNewPragma (NextPragmaInfo nextPragmaLine _) newPragma = LSP.TextEdit pragmaInsertRange $ "{-# LANGUAGE " <> pack (show newPragma) <> " #-}\n"
insertNewPragma (NextPragmaInfo _ (Just (LineSplitTextEdits ins _))) newPragma = ins { LSP._newText = "{-# LANGUAGE " <> showExtension newPragma <> " #-}\n" } :: LSP.TextEdit
insertNewPragma (NextPragmaInfo nextPragmaLine _) newPragma = LSP.TextEdit pragmaInsertRange $ "{-# LANGUAGE " <> showExtension newPragma <> " #-}\n"
where
pragmaInsertPosition = LSP.Position (fromIntegral nextPragmaLine) 0
pragmaInsertRange = LSP.Range pragmaInsertPosition pragmaInsertPosition

getFirstPragma :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT String m NextPragmaInfo
getFirstPragma (PluginId pId) state nfp = handleMaybeM "Could not get NextPragmaInfo" $ do
ghcSession <- liftIO $ runAction (T.unpack pId <> ".GhcSession") state $ useWithStale GhcSession nfp
(_, fileContents) <- liftIO $ runAction (T.unpack pId <> ".GetFileContents") state $ getFileContents nfp
case ghcSession of
Just (hscEnv -> hsc_dflags -> sessionDynFlags, _) -> pure $ Just $ getNextPragmaInfo sessionDynFlags fileContents
Nothing -> pure Nothing

-- Pre-declaration comments parser -----------------------------------------------------

-- | Each mode represents the "strongest" thing we've seen so far.
Expand Down
11 changes: 11 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ flag explicitFixity
default: True
manual: True

flag explicitFields
description: Enable explicitFields plugin
default: True
manual: True

-- formatters

flag floskell
Expand Down Expand Up @@ -300,6 +305,11 @@ common explicitFixity
build-depends: hls-explicit-fixity-plugin ^>= 1.0
cpp-options: -DexplicitFixity

common explicitFields
if flag(explicitFields)
build-depends: hls-explicit-record-fields-plugin ^>= 1.0
cpp-options: -DexplicitFields

-- formatters

common floskell
Expand Down Expand Up @@ -358,6 +368,7 @@ library
, codeRange
, gadt
, explicitFixity
, explicitFields
, floskell
, fourmolu
, ormolu
Expand Down
Loading