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 +5/−0
- LICENSE +30/−0
- hls-overloaded-record-dot-plugin.cabal +65/−0
- src/Ide/Plugin/OverloadedRecordDot.hs +280/−0
- test/Main.hs +57/−0
- test/testdata/Multiline.expected.hs +12/−0
- test/testdata/Multiline.hs +12/−0
- test/testdata/MultilineCase.expected.hs +14/−0
- test/testdata/MultilineCase.hs +13/−0
- test/testdata/MultilineExpanded.expected.hs +32/−0
- test/testdata/MultilineExpanded.hs +32/−0
- test/testdata/NestedDollar.expected.hs +17/−0
- test/testdata/NestedDollar.hs +16/−0
- test/testdata/NestedDot.expected.hs +18/−0
- test/testdata/NestedDot.hs +18/−0
- test/testdata/NestedParens.expected.hs +17/−0
- test/testdata/NestedParens.hs +16/−0
- test/testdata/NoPragmaNeeded.expected.hs +12/−0
- test/testdata/NoPragmaNeeded.hs +12/−0
- test/testdata/Simple.expected.hs +12/−0
- test/testdata/Simple.hs +11/−0
+ 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