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

Reduce usages of Prelude/Data.Text.head #2519

Closed
wants to merge 2 commits into from

Conversation

jhrcek
Copy link
Collaborator

@jhrcek jhrcek commented Dec 21, 2021

First batch of fixes to #2514

This is going to be harder than I thought :P
There are more heads to come, but opening this just to get initial feedback on the general approach.

Also (due to my ocd) I had to fix bunch of hlint warnings which lead to meaningful simplifications.

Vague questions: how are you guys dealing with running tests locally?
I'm getting bunch of fd:28: hPutBuf: resource vanished (Broken pipe) when running cabal test locally..

@jhrcek jhrcek changed the title Reduce usages of Prelude.head Reduce usages of Prelude/Data.Text.head Dec 21, 2021
@@ -34,6 +34,9 @@ buildHypothesis
where
go (occName -> occ, t)
| Just ty <- t
-- TODO what's the intention behind this check?
-- can it be replace with something like `not . isSymOcc`?
-- https://hackage.haskell.org/package/ghc-9.2.1/docs/GHC-Plugins.html#v:isSymOcc
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably question for @isovector
My intention is to get rid of this usage of head

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is checking that the bindings in scope were created in source Haskell. GHC makes lots of bindings whose name start with $ or other odd things.

You could replace it with maybe False isAlpha $ headMay $ occNameString occ

@@ -324,7 +325,7 @@ moduleUnit =
Module.moduleUnitId
#endif

filterInplaceUnits :: [UnitId] -> [PackageFlag] -> ([UnitId], [PackageFlag])
filterInplaceUnits :: NE.NonEmpty UnitId -> [PackageFlag] -> ([UnitId], [PackageFlag])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use types unqualified, hope it is the pattern for other data types

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, not sure this one should change.

@jneira
Copy link
Member

jneira commented Dec 21, 2021

I'm getting bunch of fd:28: hPutBuf: resource vanished (Broken pipe) when running cabal test locally..

I got them in windows long time ago, what is your setup?

we have some issues about, in nix and windows
https://github.com/haskell/haskell-language-server/issues?q=is%3Aissue+is%3Aopen+resource+vanished+%28Broken+pipe%29

but lately I don't get them in windows 10

Copy link
Collaborator

@pepeiborra pepeiborra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most test suites need TASTY_NUM_THREADS=1 to override the default behaviour that runs tests concurrently. The ghcide testsuite sets this implicitly I believe, but others don't.

If that's not enough, just copy the command line from CI

Comment on lines -477 to +478
let DocumentPositions{..} = head docs
let DocumentPositions{..} = headNote "Experiments.runBench" docs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the safe package is best practice anymore. It was created before we had HasCallStack but nowadays head will produce a better error message

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I think if we're going to do this we should replace it with a meaningful error, not just the position where it occurred (although that's somewhat useful).

We also could be more lenient about partial functions in things like tests and benchmarks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to have any errors escaping out in this way in ghcide.

But this particular call site is in the benchmark suite which is allowed to error out, and a position is more helpful than any description in that case.

@@ -853,18 +854,18 @@ defineNoDiagnostics op = defineEarlyCutoff $ RuleNoDiagnostics $ \k v -> (Nothin
-- | Request a Rule result if available
use :: IdeRule k v
=> k -> NormalizedFilePath -> Action (Maybe v)
use key file = head <$> uses key [file]
use key file = headNote "Development.IDE.Core.Shake.use" <$> uses key [file]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented on the issue, I think you could solve all of these at a stroke by making the underlying uses functions work on NonEmpty, which I think would be fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I support the generalization to any Traversable to use NonEmpty here


-- | Request a Rule result, it not available return the last computed result, if any, which may be stale
useWithStale :: IdeRule k v
=> k -> NormalizedFilePath -> Action (Maybe (v, PositionMapping))
useWithStale key file = head <$> usesWithStale key [file]
useWithStale key file = headNote "Development.IDE.Core.Shake.useWithStale" <$> usesWithStale key [file]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove


-- | Request a Rule result, it not available return the last computed result which may be stale.
-- Errors out if none available.
useWithStale_ :: IdeRule k v
=> k -> NormalizedFilePath -> Action (v, PositionMapping)
useWithStale_ key file = head <$> usesWithStale_ key [file]
useWithStale_ key file = headNote "Development.IDE.Core.Shake.useWithStale_" <$> usesWithStale_ key [file]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

@@ -932,7 +933,7 @@ useNoFile :: IdeRule k v => k -> Action (Maybe v)
useNoFile key = use key emptyFilePath

use_ :: IdeRule k v => k -> NormalizedFilePath -> Action v
use_ key file = head <$> uses_ key [file]
use_ key file = headNote "Development.IDE.Core.Shake.use_" <$> uses_ key [file]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

unless (null $ tail lies) $
addTrailingCommaT (head lies) -- Why we need this?
addTrailingCommaT (headNote "Development.IDE.Plugin.CodeAction.ExactPrint.extendHiding2" lies) -- Why we need this?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

@@ -210,7 +211,7 @@ extendImportHandler :: CommandFunction IdeState ExtendImport
extendImportHandler ideState edit@ExtendImport {..} = do
res <- liftIO $ runMaybeT $ extendImportHandler' ideState edit
whenJust res $ \(nfp, wedit@WorkspaceEdit {_changes}) -> do
let (_, List (head -> TextEdit {_range})) = fromJust $ _changes >>= listToMaybe . toList
let (_, List (headNote "Development.IDE.Plugin.Completions.extendImportHandler" -> TextEdit {_range})) = fromJust $ _changes >>= listToMaybe . toList
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should just do nothing if the match fails. It's already inside a whenJust for the response coming in.

@@ -63,7 +64,9 @@ lookupKind env mod =
fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod

getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]
getDocumentationTryGhc env mod n =
headNote "Development.IDE.Spans.Documentation.getDocumentationTryGhc"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems potentially kind of bad! I think we should do something here. Just return a Maybe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is similar to uses in that we are local equational reasoning suffices to prove the absence of errors. You could generalise getDocumentationsTryGhc to take Traversable and use a NonEmpty list in getDocumentationTryGhc

@@ -4886,7 +4886,9 @@ projectCompletionTests =
"import ALocal"
]
compls <- getCompletions doc (Position 1 13)
let item = head $ filter ((== "ALocalModule") . (^. Lens.label)) compls
item <- case find (\c -> c ^. Lens.label == "ALocalModule") compls of
Nothing -> liftIO . assertFailure $ "No completion with label ALocalModule found in : " <> show compls
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we enable -fno-ignore-assert ? I don't remember

Comment on lines -108 to +109
in fmap wildify . head . drop 5 $ iterated
in fmap wildify . (\xs -> atNote "Wingman.CaseSplit.iterateSplit" xs 5) $ iterated
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just use !!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this one is a bit funny because we know the list is infinite. Hmm.

@Anton-Latukha
Copy link
Collaborator

Anton-Latukha commented Dec 21, 2021

Nota bene: I addressed:

getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]

In #2349 - would merge it in some short order.

Copy link
Collaborator

@michaelpj michaelpj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good!

In future it would make this sort of thing easier to review if you don't also apply hlint suggestions. It's tempting, but reviewer bandwith is a bottleneck for us. You can always do a trivial "fix hlint suggestions PR" :)

Pepe, I'm interpreting your "Remove" comments as "go back to head here, because due to the implicit HasCallStack it is equally informative to headNote", is that right?

Comment on lines -477 to +478
let DocumentPositions{..} = head docs
let DocumentPositions{..} = headNote "Experiments.runBench" docs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I think if we're going to do this we should replace it with a meaningful error, not just the position where it occurred (although that's somewhat useful).

We also could be more lenient about partial functions in things like tests and benchmarks.

@@ -853,18 +854,18 @@ defineNoDiagnostics op = defineEarlyCutoff $ RuleNoDiagnostics $ \k v -> (Nothin
-- | Request a Rule result if available
use :: IdeRule k v
=> k -> NormalizedFilePath -> Action (Maybe v)
use key file = head <$> uses key [file]
use key file = headNote "Development.IDE.Core.Shake.use" <$> uses key [file]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I commented on the issue, I think you could solve all of these at a stroke by making the underlying uses functions work on NonEmpty, which I think would be fine.

@@ -758,7 +761,7 @@ getDependencyInfo fs = Map.fromList <$> mapM do_one fs
-- ID. Therefore we create a fake one and give them all the same unit id.
removeInplacePackages
:: UnitId -- ^ fake uid to use for our internal component
-> [UnitId]
-> NonEmpty UnitId
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps simpler to just convert to a normal list before calling this - I don't think this function needs to require its argument to be non-empty!

@@ -1553,7 +1551,7 @@ rangesForBinding' b (L l (IEThingWith _ thing _ inners labels))
rangesForBinding' _ _ = []

-- | 'matchRegex' combined with 'unifySpaces'
matchRegexUnifySpaces :: T.Text -> T.Text -> Maybe [T.Text]
matchRegexUnifySpaces :: T.Text -> T.Text -> Maybe (NonEmpty T.Text)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get why this is the return type, but it's funny to see Maybe (NonEmpty a) instead of [a]!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is funny, but in my experience it's actually the more convenient/"right" choice, whenever you want to do something special for the empty list case.

@@ -324,7 +325,7 @@ moduleUnit =
Module.moduleUnitId
#endif

filterInplaceUnits :: [UnitId] -> [PackageFlag] -> ([UnitId], [PackageFlag])
filterInplaceUnits :: NE.NonEmpty UnitId -> [PackageFlag] -> ([UnitId], [PackageFlag])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, not sure this one should change.

@@ -63,7 +64,9 @@ lookupKind env mod =
fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod

getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]
getDocumentationTryGhc env mod n =
headNote "Development.IDE.Spans.Documentation.getDocumentationTryGhc"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems potentially kind of bad! I think we should do something here. Just return a Maybe?

let gibiansky = head (filter (\s -> styleName s == "gibiansky") styles)
let gibiansky = case find (\s -> styleName s == "gibiansky") styles of
Just gibStyle -> gibStyle
Nothing -> error "Ide.Plugin.Floskell.findConfigOrDefault: Style with name 'gibiansky' not found"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just not set the appStyle if this fails?

Comment on lines -108 to +109
in fmap wildify . head . drop 5 $ iterated
in fmap wildify . (\xs -> atNote "Wingman.CaseSplit.iterateSplit" xs 5) $ iterated
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this one is a bit funny because we know the list is infinite. Hmm.

@@ -342,7 +342,7 @@ lookupNameInContext name = do
getDefiningType
:: TacticsM CType
getDefiningType = do
calling_fun_name <- fst . head <$> asks ctxDefiningFuncs
calling_fun_name <- asks (fst . headNote "Wingman.Machinery.getDefiningType" . ctxDefiningFuncs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just below we do something with failure, can we also use that branch if we fail here?

-- amount of gas we give to auto
= -- Do three passes; this should be good enough for the limited
-- amount of gas we give to auto
(\xs -> atNote "Wingman.Simplify.simplify" xs 3)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another annoying infinite list.

@pepeiborra
Copy link
Collaborator

Pepe, I'm interpreting your "Remove" comments as "go back to head here, because due to the implicit HasCallStack it is equally informative to headNote", is that right?

Correct

@jhrcek
Copy link
Collaborator Author

jhrcek commented Dec 21, 2021

Thanks for reviews, I'll address your comments tomorrow.

@jhrcek
Copy link
Collaborator Author

jhrcek commented Dec 22, 2021

I decided to close this PR as it contains too many unrelated changes.
I'll open multiple smaller, easier to review and hopefully less controversial PRs, addressing most of your comments and suggestions.

@jhrcek jhrcek closed this Dec 22, 2021
@jhrcek jhrcek deleted the jan/reduce-use-of-partial-fns-1 branch January 19, 2024 04:14
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

Successfully merging this pull request may close these issues.

7 participants