Skip to content

Commit 68d353f

Browse files
santiweightSantiago Weightpepeiborra
authored
feat: update type signature during add argument action (#3321)
* feat: update type signature during add arg action Co-authored-by: Santiago Weight <santiago.weight@lmns.com> Co-authored-by: Pepe Iborra <pepeiborra@gmail.com>
1 parent 1768fb3 commit 68d353f

23 files changed

+376
-42
lines changed

plugins/hls-refactor-plugin/src/Development/IDE/GHC/ExactPrint.hs

Lines changed: 122 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ module Development.IDE.GHC.ExactPrint
2323
#if MIN_VERSION_ghc(9,2,1)
2424
modifySmallestDeclWithM,
2525
modifyMgMatchesT,
26+
modifyMgMatchesT',
27+
modifySigWithM,
28+
genAnchor1,
2629
#endif
2730
#if !MIN_VERSION_ghc(9,2,0)
2831
Anns,
@@ -111,11 +114,18 @@ import GHC.Parser.Annotation (AnnContext (..),
111114
deltaPos)
112115
#endif
113116

117+
#if MIN_VERSION_ghc(9,2,1)
118+
import Data.List (partition)
119+
import GHC (Anchor(..), realSrcSpan, AnchorOperation, DeltaPos(..), SrcSpanAnnN)
120+
import GHC.Types.SrcLoc (generatedSrcSpan)
121+
import Control.Lens ((&), _last)
122+
import Control.Lens.Operators ((%~))
123+
#endif
124+
114125
#if MIN_VERSION_ghc(9,2,0)
115126
setPrecedingLines :: Default t => LocatedAn t a -> Int -> Int -> LocatedAn t a
116127
setPrecedingLines ast n c = setEntryDP ast (deltaPos n c)
117128
#endif
118-
119129
------------------------------------------------------------------------------
120130

121131
data Log = LogShake Shake.Log deriving Show
@@ -449,32 +459,129 @@ graftDecls dst decs0 = Graft $ \dflags a -> do
449459
--
450460
-- For example, if you would like to move a where-clause-defined variable to the same
451461
-- level as its parent HsDecl, you could use this function.
462+
--
463+
-- When matching declaration is found in the sub-declarations of `a`, `Just r` is also returned with the new `a`. If
464+
-- not declaration matched, then `Nothing` is returned.
452465
modifySmallestDeclWithM ::
453-
forall a m.
466+
forall a m r.
454467
(HasDecls a, Monad m) =>
455468
(SrcSpan -> m Bool) ->
456-
(LHsDecl GhcPs -> TransformT m [LHsDecl GhcPs]) ->
469+
(LHsDecl GhcPs -> TransformT m ([LHsDecl GhcPs], r)) ->
457470
a ->
458-
TransformT m a
471+
TransformT m (a, Maybe r)
459472
modifySmallestDeclWithM validSpan f a = do
460-
let modifyMatchingDecl [] = pure DL.empty
461-
modifyMatchingDecl (e@(L src _) : rest) =
473+
let modifyMatchingDecl [] = pure (DL.empty, Nothing)
474+
modifyMatchingDecl (ldecl@(L src _) : rest) =
462475
lift (validSpan $ locA src) >>= \case
463476
True -> do
464-
decs' <- f e
465-
pure $ DL.fromList decs' <> DL.fromList rest
466-
False -> (DL.singleton e <>) <$> modifyMatchingDecl rest
467-
modifyDeclsT (fmap DL.toList . modifyMatchingDecl) a
468-
469-
-- | Modify the each LMatch in a MatchGroup
477+
(decs', r) <- f ldecl
478+
pure $ (DL.fromList decs' <> DL.fromList rest, Just r)
479+
False -> first (DL.singleton ldecl <>) <$> modifyMatchingDecl rest
480+
modifyDeclsT' (fmap (first DL.toList) . modifyMatchingDecl) a
481+
482+
generatedAnchor :: AnchorOperation -> Anchor
483+
generatedAnchor anchorOp = GHC.Anchor (GHC.realSrcSpan generatedSrcSpan) anchorOp
484+
485+
setAnchor :: Anchor -> SrcSpanAnnN -> SrcSpanAnnN
486+
setAnchor anc (SrcSpanAnn (EpAnn _ nameAnn comments) span) =
487+
SrcSpanAnn (EpAnn anc nameAnn comments) span
488+
setAnchor _ spanAnnN = spanAnnN
489+
490+
removeTrailingAnns :: SrcSpanAnnN -> SrcSpanAnnN
491+
removeTrailingAnns (SrcSpanAnn (EpAnn anc nameAnn comments) span) =
492+
let nameAnnSansTrailings = nameAnn {nann_trailing = []}
493+
in SrcSpanAnn (EpAnn anc nameAnnSansTrailings comments) span
494+
removeTrailingAnns spanAnnN = spanAnnN
495+
496+
-- | Modify the type signature for the given IdP. This function handles splitting a multi-sig
497+
-- SigD into multiple SigD if the type signature is changed.
498+
--
499+
-- For example, update the type signature for `foo` from `Int` to `Bool`:
500+
--
501+
-- - foo :: Int
502+
-- + foo :: Bool
503+
--
504+
-- - foo, bar :: Int
505+
-- + bar :: Int
506+
-- + foo :: Bool
507+
--
508+
-- - foo, bar, baz :: Int
509+
-- + bar, baz :: Int
510+
-- + foo :: Bool
511+
modifySigWithM ::
512+
forall a m.
513+
(HasDecls a, Monad m) =>
514+
IdP GhcPs ->
515+
(LHsSigType GhcPs -> LHsSigType GhcPs) ->
516+
a ->
517+
TransformT m a
518+
modifySigWithM queryId f a = do
519+
let modifyMatchingSigD :: [LHsDecl GhcPs] -> TransformT m (DL.DList (LHsDecl GhcPs))
520+
modifyMatchingSigD [] = pure (DL.empty)
521+
modifyMatchingSigD (ldecl@(L annSigD (SigD xsig (TypeSig xTypeSig ids (HsWC xHsWc lHsSig)))) : rest)
522+
| queryId `elem` (unLoc <$> ids) = do
523+
let newSig = f lHsSig
524+
-- If this signature update caused no change, then we don't need to split up multi-signatures
525+
if newSig `geq` lHsSig
526+
then pure $ DL.singleton ldecl <> DL.fromList rest
527+
else case partition ((== queryId) . unLoc) ids of
528+
([L annMatchedId matchedId], otherIds) ->
529+
let matchedId' = L (setAnchor genAnchor0 $ removeTrailingAnns annMatchedId) matchedId
530+
matchedIdSig =
531+
let sig' = SigD xsig (TypeSig xTypeSig [matchedId'] (HsWC xHsWc newSig))
532+
epAnn = bool (noAnnSrcSpanDP generatedSrcSpan (DifferentLine 1 0)) annSigD (null otherIds)
533+
in L epAnn sig'
534+
otherSig = case otherIds of
535+
[] -> []
536+
(L (SrcSpanAnn epAnn span) id1:ids) -> [
537+
let epAnn' = case epAnn of
538+
EpAnn _ nameAnn commentsId1 -> EpAnn genAnchor0 nameAnn commentsId1
539+
EpAnnNotUsed -> EpAnn genAnchor0 mempty emptyComments
540+
ids' = L (SrcSpanAnn epAnn' span) id1:ids
541+
ids'' = ids' & _last %~ first removeTrailingAnns
542+
in L annSigD (SigD xsig (TypeSig xTypeSig ids'' (HsWC xHsWc lHsSig)))
543+
]
544+
in pure $ DL.fromList otherSig <> DL.singleton matchedIdSig <> DL.fromList rest
545+
_ -> error "multiple ids matched"
546+
modifyMatchingSigD (ldecl : rest) = (DL.singleton ldecl <>) <$> modifyMatchingSigD rest
547+
modifyDeclsT (fmap DL.toList . modifyMatchingSigD) a
548+
549+
genAnchor0 :: Anchor
550+
genAnchor0 = generatedAnchor m0
551+
552+
genAnchor1 :: Anchor
553+
genAnchor1 = generatedAnchor m1
554+
555+
-- | Apply a transformation to the decls contained in @t@
556+
modifyDeclsT' :: (HasDecls t, HasTransform m)
557+
=> ([LHsDecl GhcPs] -> m ([LHsDecl GhcPs], r))
558+
-> t -> m (t, r)
559+
modifyDeclsT' action t = do
560+
decls <- liftT $ hsDecls t
561+
(decls', r) <- action decls
562+
t' <- liftT $ replaceDecls t decls'
563+
pure (t', r)
564+
565+
-- | Modify each LMatch in a MatchGroup
470566
modifyMgMatchesT ::
471567
Monad m =>
472568
MatchGroup GhcPs (LHsExpr GhcPs) ->
473569
(LMatch GhcPs (LHsExpr GhcPs) -> TransformT m (LMatch GhcPs (LHsExpr GhcPs))) ->
474570
TransformT m (MatchGroup GhcPs (LHsExpr GhcPs))
475-
modifyMgMatchesT (MG xMg (L locMatches matches) originMg) f = do
476-
matches' <- mapM f matches
477-
pure $ MG xMg (L locMatches matches') originMg
571+
modifyMgMatchesT mg f = fst <$> modifyMgMatchesT' mg (fmap (, ()) . f) () ((.) pure . const)
572+
573+
-- | Modify the each LMatch in a MatchGroup
574+
modifyMgMatchesT' ::
575+
Monad m =>
576+
MatchGroup GhcPs (LHsExpr GhcPs) ->
577+
(LMatch GhcPs (LHsExpr GhcPs) -> TransformT m (LMatch GhcPs (LHsExpr GhcPs), r)) ->
578+
r ->
579+
(r -> r -> m r) ->
580+
TransformT m (MatchGroup GhcPs (LHsExpr GhcPs), r)
581+
modifyMgMatchesT' (MG xMg (L locMatches matches) originMg) f def combineResults = do
582+
(unzip -> (matches', rs)) <- mapM f matches
583+
r' <- lift $ foldM combineResults def rs
584+
pure $ (MG xMg (L locMatches matches') originMg, r')
478585
#endif
479586

480587
graftSmallestDeclsWithM ::

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,14 @@ import qualified Text.Fuzzy.Parallel as TFP
9393
import Text.Regex.TDFA (mrAfter,
9494
(=~), (=~~))
9595
#if MIN_VERSION_ghc(9,2,1)
96+
import Data.Either.Extra (maybeToEither)
9697
import GHC.Types.SrcLoc (generatedSrcSpan)
9798
import Language.Haskell.GHC.ExactPrint (noAnnSrcSpanDP1,
9899
runTransformT)
99100
#endif
100101
#if MIN_VERSION_ghc(9,2,0)
101-
import Extra (maybeToEither)
102+
import Control.Monad.Except (lift)
103+
import Debug.Trace
102104
import GHC (AddEpAnn (AddEpAnn),
103105
Anchor (anchor_op),
104106
AnchorOperation (..),
@@ -107,7 +109,17 @@ import GHC (AddEpAnn (Ad
107109
EpAnn (..),
108110
EpaLocation (..),
109111
LEpaComment,
110-
LocatedA)
112+
LocatedA,
113+
SrcSpanAnn' (SrcSpanAnn),
114+
SrcSpanAnnA,
115+
SrcSpanAnnN,
116+
TrailingAnn (..),
117+
addTrailingAnnToA,
118+
emptyComments,
119+
noAnn)
120+
import GHC.Hs (IsUnicodeSyntax (..))
121+
import Language.Haskell.GHC.ExactPrint.Transform (d1)
122+
111123
#else
112124
import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP),
113125
DeltaPos,
@@ -958,8 +970,6 @@ newDefinitionAction IdeOptions {..} parsedModule Range {_start} name typ
958970
-- When we receive either of these errors, we produce a text edit that will add a new argument (as a new pattern in the
959971
-- last position of each LHS of the top-level bindings for this HsDecl).
960972
--
961-
-- TODO Include logic to also update the type signature of a binding
962-
--
963973
-- NOTE When adding a new argument to a declaration, the corresponding argument's type in declaration's signature might
964974
-- not be the last type in the signature, such as:
965975
-- foo :: a -> b -> c -> d
@@ -973,31 +983,100 @@ suggestAddArgument parsedModule Diagnostic {_message, _range}
973983
where
974984
message = unifySpaces _message
975985

976-
-- TODO use typ to modify type signature
986+
-- Given a name for the new binding, add a new pattern to the match in the last position,
987+
-- returning how many patterns there were in this match prior to the transformation:
988+
-- addArgToMatch "foo" `bar arg1 arg2 = ...`
989+
-- => (`bar arg1 arg2 foo = ...`, 2)
990+
addArgToMatch :: T.Text -> GenLocated l (Match GhcPs body) -> (GenLocated l (Match GhcPs body), Int)
991+
addArgToMatch name (L locMatch (Match xMatch ctxMatch pats rhs)) =
992+
let unqualName = mkRdrUnqual $ mkVarOcc $ T.unpack name
993+
newPat = L (noAnnSrcSpanDP1 generatedSrcSpan) $ VarPat NoExtField (noLocA unqualName)
994+
in (L locMatch (Match xMatch ctxMatch (pats <> [newPat]) rhs), length pats)
995+
996+
-- Attempt to insert a binding pattern into each match for the given LHsDecl; succeeds only if the function is a FunBind.
997+
-- Also return:
998+
-- - the declaration's name
999+
-- - the number of bound patterns in the declaration's matches prior to the transformation
1000+
--
1001+
-- For example:
1002+
-- insertArg "new_pat" `foo bar baz = 1`
1003+
-- => (`foo bar baz new_pat = 1`, Just ("foo", 2))
1004+
appendFinalPatToMatches :: T.Text -> LHsDecl GhcPs -> TransformT (Either ResponseError) (LHsDecl GhcPs, Maybe (GenLocated SrcSpanAnnN RdrName, Int))
1005+
appendFinalPatToMatches name = \case
1006+
(L locDecl (ValD xVal (FunBind xFunBind idFunBind mg coreFunBind))) -> do
1007+
(mg', numPatsMay) <- modifyMgMatchesT' mg (pure . second Just . addArgToMatch name) Nothing combineMatchNumPats
1008+
numPats <- lift $ maybeToEither (responseError "Unexpected empty match group in HsDecl") numPatsMay
1009+
let decl' = L locDecl (ValD xVal (FunBind xFunBind idFunBind mg' coreFunBind))
1010+
pure (decl', Just (idFunBind, numPats))
1011+
decl -> pure (decl, Nothing)
1012+
where
1013+
combineMatchNumPats Nothing other = pure other
1014+
combineMatchNumPats other Nothing = pure other
1015+
combineMatchNumPats (Just l) (Just r)
1016+
| l == r = pure (Just l)
1017+
| otherwise = Left $ responseError "Unexpected different numbers of patterns in HsDecl MatchGroup"
1018+
1019+
-- The add argument works as follows:
1020+
-- 1. Attempt to add the given name as the last pattern of the declaration that contains `range`.
1021+
-- 2. If such a declaration exists, use that declaration's name to modify the signature of said declaration, if it
1022+
-- has a type signature.
1023+
--
1024+
-- NOTE For the following situation, the type signature is not updated (it's unclear what should happen):
1025+
-- type FunctionTySyn = () -> Int
1026+
-- foo :: FunctionTySyn
1027+
-- foo () = new_def
1028+
--
1029+
-- TODO instead of inserting a typed hole; use GHC's suggested type from the error
9771030
addArgumentAction :: ParsedModule -> Range -> T.Text -> Maybe T.Text -> Either ResponseError [(T.Text, [TextEdit])]
978-
addArgumentAction (ParsedModule _ parsedSource _ _) range name _typ =
979-
do
980-
let addArgToMatch (L locMatch (Match xMatch ctxMatch pats rhs)) = do
981-
let unqualName = mkRdrUnqual $ mkVarOcc $ T.unpack name
982-
let newPat = L (noAnnSrcSpanDP1 generatedSrcSpan) $ VarPat NoExtField (noLocA unqualName)
983-
pure $ L locMatch (Match xMatch ctxMatch (pats <> [newPat]) rhs)
984-
insertArg = \case
985-
(L locDecl (ValD xVal (FunBind xFunBind idFunBind mg coreFunBind))) -> do
986-
mg' <- modifyMgMatchesT mg addArgToMatch
987-
let decl' = L locDecl (ValD xVal (FunBind xFunBind idFunBind mg' coreFunBind))
988-
pure [decl']
989-
decl -> pure [decl]
990-
case runTransformT $ modifySmallestDeclWithM spanContainsRangeOrErr insertArg (makeDeltaAst parsedSource) of
991-
Left err -> Left err
992-
Right (newSource, _, _) ->
993-
let diff = makeDiffTextEdit (T.pack $ exactPrint parsedSource) (T.pack $ exactPrint newSource)
994-
in pure [("Add argument ‘" <> name <> "’ to function", fromLspList diff)]
995-
where
996-
spanContainsRangeOrErr = maybeToEither (responseError "SrcSpan was not valid range") . (`spanContainsRange` range)
997-
#endif
1031+
addArgumentAction (ParsedModule _ moduleSrc _ _) range name _typ = do
1032+
(newSource, _, _) <- runTransformT $ do
1033+
(moduleSrc', join -> matchedDeclNameMay) <- addNameAsLastArgOfMatchingDecl (makeDeltaAst moduleSrc)
1034+
case matchedDeclNameMay of
1035+
Just (matchedDeclName, numPats) -> modifySigWithM (unLoc matchedDeclName) (addTyHoleToTySigArg numPats) moduleSrc'
1036+
Nothing -> pure moduleSrc'
1037+
let diff = makeDiffTextEdit (T.pack $ exactPrint moduleSrc) (T.pack $ exactPrint newSource)
1038+
pure [("Add argument ‘" <> name <> "’ to function", fromLspList diff)]
1039+
where
1040+
addNameAsLastArgOfMatchingDecl = modifySmallestDeclWithM spanContainsRangeOrErr addNameAsLastArg
1041+
addNameAsLastArg = fmap (first (:[])) . appendFinalPatToMatches name
1042+
1043+
spanContainsRangeOrErr = maybeToEither (responseError "SrcSpan was not valid range") . (`spanContainsRange` range)
1044+
1045+
-- Transform an LHsType into a list of arguments and return type, to make transformations easier.
1046+
hsTypeToFunTypeAsList :: LHsType GhcPs -> ([(SrcSpanAnnA, XFunTy GhcPs, HsArrow GhcPs, LHsType GhcPs)], LHsType GhcPs)
1047+
hsTypeToFunTypeAsList = \case
1048+
L spanAnnA (HsFunTy xFunTy arrow lhs rhs) ->
1049+
let (rhsArgs, rhsRes) = hsTypeToFunTypeAsList rhs
1050+
in ((spanAnnA, xFunTy, arrow, lhs):rhsArgs, rhsRes)
1051+
ty -> ([], ty)
1052+
1053+
-- The inverse of `hsTypeToFunTypeAsList`
1054+
hsTypeFromFunTypeAsList :: ([(SrcSpanAnnA, XFunTy GhcPs, HsArrow GhcPs, LHsType GhcPs)], LHsType GhcPs) -> LHsType GhcPs
1055+
hsTypeFromFunTypeAsList (args, res) =
1056+
foldr (\(spanAnnA, xFunTy, arrow, argTy) res -> L spanAnnA $ HsFunTy xFunTy arrow argTy res) res args
1057+
1058+
-- Add a typed hole to a type signature in the given argument position:
1059+
-- 0 `foo :: ()` => foo :: _ -> ()
1060+
-- 2 `foo :: FunctionTySyn` => foo :: FunctionTySyn
1061+
-- 1 `foo :: () -> () -> Int` => foo :: () -> _ -> () -> Int
1062+
addTyHoleToTySigArg :: Int -> LHsSigType GhcPs -> (LHsSigType GhcPs)
1063+
addTyHoleToTySigArg loc (L annHsSig (HsSig xHsSig tyVarBndrs lsigTy)) =
1064+
let (args, res) = hsTypeToFunTypeAsList lsigTy
1065+
wildCardAnn = SrcSpanAnn (EpAnn genAnchor1 (AnnListItem [AddRarrowAnn d1]) emptyComments) generatedSrcSpan
1066+
newArg = (SrcSpanAnn mempty generatedSrcSpan, noAnn, HsUnrestrictedArrow NormalSyntax, L wildCardAnn $ HsWildCardTy noExtField)
1067+
-- NOTE if the location that the argument wants to be placed at is not one more than the number of arguments
1068+
-- in the signature, then we return the original type signature.
1069+
-- This situation most likely occurs due to a function type synonym in the signature
1070+
insertArg n _ | n < 0 = error "Not possible"
1071+
insertArg 0 as = newArg:as
1072+
insertArg _ [] = []
1073+
insertArg n (a:as) = a : insertArg (n - 1) as
1074+
lsigTy' = hsTypeFromFunTypeAsList (insertArg loc args, res)
1075+
in L annHsSig (HsSig xHsSig tyVarBndrs lsigTy')
9981076

9991077
fromLspList :: List a -> [a]
10001078
fromLspList (List a) = a
1079+
#endif
10011080

10021081
suggestFillTypeWildcard :: Diagnostic -> [(T.Text, TextEdit)]
10031082
suggestFillTypeWildcard Diagnostic{_range=_range,..}

plugins/hls-refactor-plugin/test/Main.hs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ import Test.Tasty.HUnit
5454
import Text.Regex.TDFA ((=~))
5555

5656

57-
import Development.IDE.Plugin.CodeAction (matchRegExMultipleImports)
57+
import Development.IDE.Plugin.CodeAction (bindingsPluginDescriptor,
58+
matchRegExMultipleImports)
5859
import Test.Hls
5960

6061
import Control.Applicative (liftA2)
@@ -2371,10 +2372,38 @@ addFunctionArgumentTests =
23712372
liftIO $ actionTitle @?= "Add argument ‘select’ to function"
23722373
executeCodeAction action
23732374
contentAfterAction <- documentContents docB
2374-
liftIO $ contentAfterAction @?= T.unlines foo'
2375+
liftIO $ contentAfterAction @?= T.unlines foo',
2376+
mkGoldenAddArgTest "AddArgWithSig" (R 1 0 1 50),
2377+
mkGoldenAddArgTest "AddArgWithSigAndDocs" (R 8 0 8 50),
2378+
mkGoldenAddArgTest "AddArgFromLet" (R 2 0 2 50),
2379+
mkGoldenAddArgTest "AddArgFromWhere" (R 3 0 3 50),
2380+
mkGoldenAddArgTest "AddArgWithTypeSynSig" (R 2 0 2 50),
2381+
mkGoldenAddArgTest "AddArgWithTypeSynSigContravariant" (R 2 0 2 50),
2382+
mkGoldenAddArgTest "AddArgWithLambda" (R 1 0 1 50),
2383+
mkGoldenAddArgTest "MultiSigFirst" (R 2 0 2 50),
2384+
mkGoldenAddArgTest "MultiSigLast" (R 2 0 2 50),
2385+
mkGoldenAddArgTest "MultiSigMiddle" (R 2 0 2 50)
23752386
]
23762387
#endif
23772388

2389+
mkGoldenAddArgTest :: FilePath -> Range -> TestTree
2390+
mkGoldenAddArgTest testFileName range = do
2391+
let action docB = do
2392+
_ <- waitForDiagnostics
2393+
InR action@CodeAction {_title = actionTitle} : _ <-
2394+
filter (\(InR CodeAction {_title = x}) -> "Add" `isPrefixOf` T.unpack x)
2395+
<$> getCodeActions docB range
2396+
liftIO $ actionTitle @?= "Add argument ‘new_def’ to function"
2397+
executeCodeAction action
2398+
goldenWithHaskellDoc
2399+
(Refactor.bindingsPluginDescriptor mempty "ghcide-code-actions-bindings")
2400+
(testFileName <> " (golden)")
2401+
"test/data/golden/add-arg"
2402+
testFileName
2403+
"expected"
2404+
"hs"
2405+
action
2406+
23782407
deleteUnusedDefinitionTests :: TestTree
23792408
deleteUnusedDefinitionTests = testGroup "delete unused definition action"
23802409
[ testSession "delete unused top level binding" $
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo :: Bool -> _ -> Int
2+
foo True new_def =
3+
let bar = new_def
4+
in bar
5+
6+
foo False new_def = 1

0 commit comments

Comments
 (0)