packages feed

hls-overloaded-record-dot-plugin (empty) → 2.0.0.1

raw patch · 21 files changed

+701/−0 lines, 21 filesdep +basedep +containersdep +deepseq

Dependencies added: base, containers, deepseq, filepath, ghc-boot-th, ghcide, hls-graph, hls-overloaded-record-dot-plugin, hls-plugin-api, hls-test-utils, lens, lsp, lsp-test, syb, text, transformers, unordered-containers

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for hls-overloaded-record-dot-plugin++## 1.0.0.0 -- 2023-04-16++* First version. 
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Nathan Maxson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Nathan Maxson nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ hls-overloaded-record-dot-plugin.cabal view
@@ -0,0 +1,65 @@+cabal-version:      3.0+name:               hls-overloaded-record-dot-plugin+version:            2.0.0.1+synopsis:           Overloaded record dot plugin for Haskell Language Server+description:+  Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>+license:            BSD-3-Clause+license-file:       LICENSE+author:             Nathan Maxson+maintainer:         joyfulmantis@gmail.com+category:           Development+build-type:         Simple+extra-doc-files:    CHANGELOG.md+extra-source-files:+  test/testdata/**/*.hs++source-repository head+  type:     git+  location: https://github.com/haskell/haskell-language-server++common warnings+    ghc-options: -Wall++library+    import:           warnings+    if impl(ghc < 9.2)+      buildable: False+    else+      buildable: True+    exposed-modules:  Ide.Plugin.OverloadedRecordDot+    build-depends:+      , base                  >=4.16 && <5+      , ghcide+      , hls-plugin-api+      , lsp+      , lens+      , hls-graph+      , text+      , syb+      , transformers+      , ghc-boot-th+      , unordered-containers+      , containers+      , deepseq+    hs-source-dirs:   src+    default-language: GHC2021++test-suite tests+    import:           warnings+    if impl(ghc < 9.2)+      buildable: False+    else+      buildable: True+    default-language: GHC2021+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          Main.hs+    build-depends:+      , base+      , filepath+      , text+      , hls-overloaded-record-dot-plugin+      , lsp-test+      , hls-test-utils+
+ src/Ide/Plugin/OverloadedRecordDot.hs view
@@ -0,0 +1,280 @@+{-# LANGUAGE CPP               #-}+{-# LANGUAGE DataKinds         #-}+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms   #-}+{-# LANGUAGE TypeFamilies      #-}+{-# LANGUAGE ViewPatterns      #-}++module Ide.Plugin.OverloadedRecordDot+  ( descriptor+  , Log+  ) where++-- based off of Berk Okzuturk's hls-explicit-records-fields-plugin++import           Control.Lens                         ((^.))+import           Control.Monad.IO.Class               (MonadIO, liftIO)+import           Control.Monad.Trans.Except           (ExceptT)+import           Data.Generics                        (GenericQ, everything,+                                                       everythingBut, mkQ)+import qualified Data.HashMap.Strict                  as HashMap+import           Data.Maybe                           (mapMaybe, maybeToList)+import           Data.Text                            (Text)+import           Development.IDE                      (IdeState,+                                                       NormalizedFilePath,+                                                       Pretty (..), Range,+                                                       Recorder (..), Rules,+                                                       WithPriority (..),+                                                       realSrcSpanToRange)+import           Development.IDE.Core.Rules           (runAction)+import           Development.IDE.Core.RuleTypes       (TcModuleResult (..),+                                                       TypeCheck (..))+import           Development.IDE.Core.Shake           (define, use,+                                                       useWithStale)+import qualified Development.IDE.Core.Shake           as Shake++#if __GLASGOW_HASKELL__ >= 903+import           Development.IDE.GHC.Compat           (HsExpr (HsRecSel))+#else+import           Development.IDE.GHC.Compat           (HsExpr (HsRecFld))+#endif++import           Control.DeepSeq                      (rwhnf)+import           Development.IDE.Core.PositionMapping (PositionMapping (PositionMapping),+                                                       toCurrentRange)+import           Development.IDE.GHC.Compat           (Extension (OverloadedRecordDot),+                                                       GhcPass,+                                                       HsExpansion (HsExpanded),+                                                       HsExpr (HsApp, HsPar, HsVar, OpApp, XExpr),+                                                       LHsExpr, Outputable,+                                                       Pass (..), RealSrcSpan,+                                                       appPrec, dollarName,+                                                       getLoc, hs_valds,+                                                       parenthesizeHsContext,+                                                       parenthesizeHsExpr,+                                                       pattern RealSrcSpan,+                                                       unLoc)+import           Development.IDE.GHC.Util             (getExtensions,+                                                       printOutputable)+import           Development.IDE.Graph                (RuleResult)+import           Development.IDE.Graph.Classes        (Hashable, NFData (rnf))+import           Development.IDE.Spans.Pragmas        (NextPragmaInfo (..),+                                                       getFirstPragma,+                                                       insertNewPragma)+import           Development.IDE.Types.Logger         (Priority (..),+                                                       cmapWithPrio, logWith,+                                                       (<+>))+import           GHC.Generics                         (Generic)+import           Ide.Plugin.RangeMap                  (RangeMap)+import qualified Ide.Plugin.RangeMap                  as RangeMap+import           Ide.PluginUtils                      (getNormalizedFilePath,+                                                       handleMaybeM,+                                                       pluginResponse)+import           Ide.Types                            (PluginDescriptor (..),+                                                       PluginId (..),+                                                       PluginMethodHandler,+                                                       defaultPluginDescriptor,+                                                       mkPluginHandler)+import           Language.LSP.Types                   (CodeAction (..),+                                                       CodeActionKind (CodeActionRefactorRewrite),+                                                       CodeActionParams (..),+                                                       Command, List (..),+                                                       Method (..),+                                                       SMethod (..),+                                                       TextEdit (..),+                                                       WorkspaceEdit (WorkspaceEdit),+                                                       fromNormalizedUri,+                                                       normalizedFilePathToUri,+                                                       type (|?) (InR))+import qualified Language.LSP.Types.Lens              as L+data Log+    = LogShake Shake.Log+    | LogCollectedRecordSelectors [RecordSelectorExpr]+    | LogTextEdits [TextEdit]++instance Pretty Log where+    pretty = \case+        LogShake shakeLog -> pretty shakeLog+        LogCollectedRecordSelectors recs -> "Collected record selectors:"+                                                <+> pretty recs++data CollectRecordSelectors = CollectRecordSelectors+                    deriving (Eq, Show, Generic)++instance Hashable CollectRecordSelectors+instance NFData CollectRecordSelectors++data CollectRecordSelectorsResult = CRSR+    { recordInfos       :: RangeMap RecordSelectorExpr+    , enabledExtensions :: [Extension]+    }+    deriving (Generic)++instance NFData CollectRecordSelectorsResult++instance Show CollectRecordSelectorsResult where+    show _ = "<CollectRecordsResult>"++type instance RuleResult CollectRecordSelectors = CollectRecordSelectorsResult++-- |Where we store our collected record selectors+data RecordSelectorExpr = RecordSelectorExpr+    { -- |The location of the matched expression+    location     :: Range,+    -- |The record selector, this is found in front of recordExpr, but get's+    -- placed after it when converted into record dot syntax+    selectorExpr :: LHsExpr (GhcPass 'Renamed),+    -- |The record expression. The only requirement is that it evaluates to a+    -- record in the end+    recordExpr   :: LHsExpr (GhcPass 'Renamed) }++instance Pretty RecordSelectorExpr where+    pretty (RecordSelectorExpr l rs se) = pretty (printOutputable rs) <> ":"+                                            <+> pretty (printOutputable se)++instance NFData RecordSelectorExpr where+  rnf = rwhnf++descriptor :: Recorder (WithPriority Log) -> PluginId+                -> PluginDescriptor IdeState+descriptor recorder plId = (defaultPluginDescriptor plId)+    { pluginHandlers =+        mkPluginHandler STextDocumentCodeAction codeActionProvider+    , pluginRules = collectRecSelsRule recorder+    }++codeActionProvider :: PluginMethodHandler IdeState 'TextDocumentCodeAction+codeActionProvider ideState pId (CodeActionParams _ _ caDocId caRange _) =+    pluginResponse $ do+        nfp <- getNormalizedFilePath (caDocId ^. L.uri)+        pragma <- getFirstPragma pId ideState nfp+        CRSR crsMap exts <- collectRecSelResult ideState nfp+        let pragmaEdit =+                if OverloadedRecordDot `elem` exts+                then Nothing+                else Just $ insertNewPragma pragma OverloadedRecordDot+            edits crs = convertRecordSelectors crs : maybeToList pragmaEdit+            changes crs =+                Just $ HashMap.singleton (fromNormalizedUri+                                            (normalizedFilePathToUri nfp))+                                            (List (edits crs))+            mkCodeAction crs = InR CodeAction+                { -- We pass the record selector to the title function, so that+                  -- we can have the name of the record selector in the title of+                  -- the codeAction. This allows the user can easily distinguish+                  -- between the different codeActions when using nested record+                  -- selectors, the disadvantage is we need to print out the+                  -- name of the record selector which will decrease performance+                _title = mkCodeActionTitle exts crs+                , _kind = Just CodeActionRefactorRewrite+                , _diagnostics = Nothing+                , _isPreferred = Nothing+                , _disabled = Nothing+                , _edit = Just $ WorkspaceEdit (changes crs) Nothing Nothing+                , _command = Nothing+                , _xdata = Nothing+                }+            actions = map mkCodeAction (RangeMap.filterByRange caRange crsMap)+        pure $ List actions+    where+    mkCodeActionTitle :: [Extension] -> RecordSelectorExpr-> Text+    mkCodeActionTitle exts (RecordSelectorExpr _ se _) =+        if OverloadedRecordDot `elem` exts+            then title+            else title <> " (needs extension: OverloadedRecordDot)"+        where+            title = "Convert `" <> name <> "` to record dot syntax"+            name = printOutputable se++collectRecSelsRule :: Recorder (WithPriority Log) -> Rules ()+collectRecSelsRule recorder = define (cmapWithPrio LogShake recorder) $+    \CollectRecordSelectors nfp ->+        useWithStale TypeCheck nfp >>= \case+        -- `useWithStale` here allows us to be able to return codeActions even+        -- if the file does not typecheck. The disadvantage being that we+        -- sometimes will end up corrupting code. This is most obvious in that+        -- used code actions will continue to be presented, and when applied+        -- multiple times will almost always cause code corruption.+        Nothing -> pure ([], Nothing)+        Just (tmr, pm) -> do+            let -- We need the file's extensions to check whether we need to add+                -- the OverloadedRecordDot pragma+                exts = getEnabledExtensions tmr+                recSels = mapMaybe (rewriteRange pm) (getRecordSelectors tmr)+            logWith recorder Debug (LogCollectedRecordSelectors recSels)+            let -- We need the rangeMap to be able to filter by range later+                crsMap :: RangeMap RecordSelectorExpr+                crsMap = RangeMap.fromList location recSels+            pure ([], CRSR <$> Just crsMap <*> Just exts)+    where getEnabledExtensions :: TcModuleResult -> [Extension]+          getEnabledExtensions = getExtensions . tmrParsed+          getRecordSelectors :: TcModuleResult -> [RecordSelectorExpr]+          getRecordSelectors (tmrRenamed -> (hs_valds -> valBinds,_,_,_)) =+            collectRecordSelectors valBinds+          rewriteRange :: PositionMapping -> RecordSelectorExpr+                            -> Maybe RecordSelectorExpr+          rewriteRange pm recSel =+            case toCurrentRange pm (location recSel) of+                Just newLoc -> Just $ recSel{location = newLoc}+                Nothing     -> Nothing++convertRecordSelectors :: RecordSelectorExpr ->  TextEdit+convertRecordSelectors (RecordSelectorExpr l se re) =+    TextEdit l $ convertRecSel se re++-- |Converts a record selector expression into record dot syntax, currently we+-- are using printOutputable to do it. We are also letting GHC decide when to+-- parenthesize the record expression+convertRecSel :: Outputable (LHsExpr (GhcPass 'Renamed))+                    => LHsExpr (GhcPass 'Renamed)+                    -> LHsExpr (GhcPass 'Renamed) -> Text+convertRecSel se re = printOutputable (parenthesizeHsExpr appPrec re) <> "."+                        <> printOutputable se++collectRecordSelectors :: GenericQ [RecordSelectorExpr]+-- It's important that we use everthingBut here, because if we used everything+-- we would get duplicates for every case that occurs inside a HsExpanded+-- expression. Please see the test MultilineExpanded.hs+collectRecordSelectors = everythingBut (<>) (([], False) `mkQ` getRecSels)++-- |We want to return a list here, because on the occasion that we encounter a+-- HsExpanded expression, we want to return all the results from recursing on+-- one branch, which could be multiple matches. Again see MultilineExpanded.hs+getRecSels :: LHsExpr (GhcPass 'Renamed) -> ([RecordSelectorExpr], Bool)+-- When we stumble upon an occurrence of HsExpanded, we only want to follow one+-- branch. We do this here, by explicitly returning occurrences from traversing+-- the original branch, and returning True, which keeps syb from implicitly+-- continuing to traverse.+getRecSels (unLoc -> XExpr (HsExpanded a _)) = (collectRecordSelectors a, True)+#if __GLASGOW_HASKELL__ >= 903+-- applied record selection: "selector record" or "selector (record)" or+-- "selector selector2.record2"+getRecSels e@(unLoc -> HsApp _ se@(unLoc -> HsRecSel _ _) re) =+    ( [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re+      | RealSrcSpan realSpan' _ <- [ getLoc e ] ], False )+-- Record selection where the field is being applied with the "$" operator:+-- "selector $ record"+getRecSels e@(unLoc -> OpApp _ se@(unLoc -> HsRecSel _ _)+                        (unLoc -> HsVar _ (unLoc -> d)) re) | d == dollarName =+    ( [ RecordSelectorExpr (realSrcSpanToRange realSpan')  se re+      | RealSrcSpan realSpan' _ <- [ getLoc e ] ], False )+#else+getRecSels e@(unLoc -> HsApp _ se@(unLoc -> HsRecFld _ _) re) =+    ( [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re+      | RealSrcSpan realSpan' _ <- [ getLoc e ] ], False )+getRecSels e@(unLoc -> OpApp _ se@(unLoc -> HsRecFld _ _)+                        (unLoc -> HsVar _ (unLoc -> d)) re) | d == dollarName =+    ( [ RecordSelectorExpr (realSrcSpanToRange realSpan')  se re+      | RealSrcSpan realSpan' _ <- [ getLoc e ] ], False )+#endif+getRecSels _ = ([], False)++collectRecSelResult :: MonadIO m => IdeState -> NormalizedFilePath+                        -> ExceptT String m CollectRecordSelectorsResult+collectRecSelResult ideState =+    handleMaybeM "Unable to TypeCheck"+        . liftIO+        . runAction "overloadedRecordDot.collectRecordSelectors" ideState+        . use CollectRecordSelectors+
+ test/Main.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns        #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE TypeOperators         #-}++module Main ( main ) where++import           Data.Either                    (rights)+import qualified Data.Text                      as T+import qualified Ide.Plugin.OverloadedRecordDot as OverloadedRecordDot+import           System.FilePath                ((</>))+import           Test.Hls+++main :: IO ()+main = defaultTestRunner test++plugin :: PluginTestDescriptor OverloadedRecordDot.Log+plugin = mkPluginTestDescriptor OverloadedRecordDot.descriptor "overloaded-record-dot"++test :: TestTree+test = testGroup "overloaded-record-dot"+  [ mkTest "Simple" "Simple" "name" 10 7 10 15,+    mkTest "NoPragmaNeeded" "NoPragmaNeeded" "name" 11 7 11 15,+    mkTest "NestedParens" "NestedParens" "name" 15 7 15 24,+    mkTest "NestedDot" "NestedDot" "name" 17 7 17 22,+    mkTest "NestedDollar" "NestedDollar" "name" 15 7 15 24,+    mkTest "MultilineCase" "MultilineCase" "name" 10 7 12 15,+    mkTest "Multiline" "Multiline" "name" 10 7 11 15,+    mkTest "MultilineExpanded" "MultilineExpanded" "owner" 28 8 28 19+  ]++mkTest :: TestName -> FilePath -> T.Text -> UInt -> UInt -> UInt -> UInt -> TestTree+mkTest title fp selectorName x1 y1 x2 y2 =+  goldenWithHaskellDoc plugin title testDataDir fp "expected" "hs" $ \doc -> do+    (act:_) <- getExplicitFieldsActions doc selectorName x1 y1 x2 y2+    executeCodeAction act++getExplicitFieldsActions+  :: TextDocumentIdentifier+  -> T.Text+  -> UInt -> UInt -> UInt -> UInt+  -> Session [CodeAction]+getExplicitFieldsActions doc selectorName x1 y1 x2 y2 =+  findExplicitFieldsAction selectorName <$> getCodeActions doc range+  where+    range = Range (Position x1 y1) (Position x2 y2)++findExplicitFieldsAction :: T.Text  -> [a |? CodeAction] -> [CodeAction]+findExplicitFieldsAction selectorName = filter (isExplicitFieldsCodeAction selectorName) . rights . map toEither++isExplicitFieldsCodeAction :: T.Text -> CodeAction -> Bool+isExplicitFieldsCodeAction selectorName CodeAction {_title} =+  ("Convert `" <> selectorName <> "` to record dot syntax") `T.isPrefixOf` _title++testDataDir :: FilePath+testDataDir = "test" </> "testdata"
+ test/testdata/Multiline.expected.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = man.name
+ test/testdata/Multiline.hs view
@@ -0,0 +1,12 @@+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = name+            man
+ test/testdata/MultilineCase.expected.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+  putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test ::  String+test = (case True of+   True -> man+   False -> man).name
+ test/testdata/MultilineCase.hs view
@@ -0,0 +1,13 @@+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+  putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test ::  String+test = name $ case True of+   True  -> man+   False -> man
+ test/testdata/MultilineExpanded.expected.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedRecordDot #-}++data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+  putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++home2 :: Building+home2 = Building {address = "No. 6 Beach Ave.", owner = man}++home3 :: Building+home3 = Building {address = "No. 12 Central Blvd.", owner = man}++n:: Int+n = 3++test :: String+test = (case n of+   0  -> owner home+   1 -> home2.owner+   2 -> owner home3+   _ -> man).name+
+ test/testdata/MultilineExpanded.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedRecordDot #-}++data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+  putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++home2 :: Building+home2 = Building {address = "No. 6 Beach Ave.", owner = man}++home3 :: Building+home3 = Building {address = "No. 12 Central Blvd.", owner = man}++n:: Int+n = 3++test :: String+test = (case n of+   0  -> owner home+   1 -> owner home2+   2 -> owner home3+   _ -> man).name+
+ test/testdata/NestedDollar.expected.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = (owner home).name
+ test/testdata/NestedDollar.hs view
@@ -0,0 +1,16 @@+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = name $ owner home
+ test/testdata/NestedDot.expected.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedRecordDot #-}++data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = home.owner.name
+ test/testdata/NestedDot.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedRecordDot #-}++data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = name home.owner
+ test/testdata/NestedParens.expected.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = (owner home).name
+ test/testdata/NestedParens.hs view
@@ -0,0 +1,16 @@+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++data Building = Building {address :: String, owner :: Happy}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++home :: Building+home = Building {address = "No. 10 Privet Dr.", owner = man}++test :: String+test = name (owner home)
+ test/testdata/NoPragmaNeeded.expected.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = man.name
+ test/testdata/NoPragmaNeeded.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = name man
+ test/testdata/Simple.expected.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE OverloadedRecordDot #-}+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = man.name
+ test/testdata/Simple.hs view
@@ -0,0 +1,11 @@+data Happy = Happy {name :: String, age :: Int, happy :: Bool}++main :: IO ()+main = do+    putStrLn test++man :: Happy+man = Happy {name = "Happy", age = 1, happy = True}++test :: String+test = name man