hlint icon indicating copy to clipboard operation
hlint copied to clipboard

Default to the same extensions as GHC

Open asarkar opened this issue 3 years ago • 17 comments

The following function is part of a module.

type Trio = (Maybe Integer, String, String)

parse :: String -> Trio
parse text = (n, op, s)
  where
    pattern = "[-]?[[:digit:]]+"
    (prefix, num, suffix) = text =~ pattern :: (String, String, String)
    trim = T.unpack . T.strip . T.pack
    n = R.readMaybe $ trim num
    op = trim prefix
    s = trim suffix

This compiles and works for the given test cases, but hlint emits an error.

Error: Parse error: on input `='
Found:
    parse text = (n, op, s)
      where
  >     pattern = "[-]?[[:digit:]]+"
        (prefix, num, suffix) = text =~ pattern :: (String, String, String)
        trim = T.unpack . T.strip . T.pack 

I've gone through most of the tickets that reported similar errors, and the suggestion is to disable TemplateHaskell using the pragma {-# LANGUAGE NoTemplateHaskell #-}. However, that doesn't help in this case.

asarkar avatar Jul 17 '22 04:07 asarkar

I can confirm that using the pragma {-# LANGUAGE NoPatternSynonyms #-} fixes the error. Can hlint not turn on language extensions magically?

asarkar avatar Jul 17 '22 06:07 asarkar

@asarkar HLint could do anything it wanted. But it's a balance - do we break more users by turning on PatternSynonyms, or fix more users? I think the view is that it fixes more users than it breaks, so on by default is the right default. If you pass -XHaskell2020 or similar I think it restricts you to a specific language standard, so you can opt in to following a standard rather than the default behaviour.

ndmitchell avatar Jul 17 '22 10:07 ndmitchell

What I don’t understand, perhaps because of my lack of experience working with Haskell, is that why hlint can’t use the user-provided configuration, if any, instead of imposing its own opinion about what’s good for the user?

asarkar avatar Jul 17 '22 20:07 asarkar

@asarkar hlint will use the user provided configuration if it is given one. as @ndmitchell recommends pass -XGhc2021 (or -XHaskell2010 or -XHaskell98) to hlint. doing this you have precisely specified what base set of extensions are enabled.

shayne-fletcher avatar Jul 17 '22 21:07 shayne-fletcher

GHC2021 is used by GHC if neither Haskell98 nor Haskell2010 is turned on explicitly.

It seems to be the default without me having to specify explicitly. That’s the way I’d like to keep it, so that the code is always compatible with the latest language specification. Notably, PatternSynonyms is not enabled by GHC2021.

asarkar avatar Jul 17 '22 22:07 asarkar

GHC2021 is used by GHC if neither Haskell98 nor Haskell2010 is turned on explicitly.

It seems to be the default without me having to specify explicitly. That’s the way I’d like to keep it, so that the code is always compatible with the latest language specification. Notably, PatternSynonyms is not enabled by GHC2021.

if no language specifier is provided hlint constructs its own set of extensions to default enable. that set enables more extensions than GHC would default enable if not invoked with an explicit language specifier.

update: i confirm that PatternSynonyms is in that set.

shayne-fletcher avatar Jul 17 '22 22:07 shayne-fletcher

the default behavior is chosen for hlint to parse as many files as possible (i.e. default behavior = not give a language specifier).

shayne-fletcher avatar Jul 17 '22 22:07 shayne-fletcher

if no language specifier is provided hlint constructs its own set of extensions

... which is different from what GHC chooses, hence the problem. Why not choose the default the same as GHC?

hlint to parse as many files as possible

Is there any substantiated data that shows the difference between hlint chosen extensions and GHC chosen extensions? In other words, can it be shown that the if hlint chose the same default extensions as GHC, it'd fail to parse x% of files?

asarkar avatar Jul 18 '22 01:07 asarkar

if no language specifier is provided hlint constructs its own set of extensions

... which is different from what GHC chooses, hence the problem. Why not choose the default the same as GHC?

i agree this is a reasonable proposition.

hlint to parse as many files as possible

Is there any substantiated data that shows the difference between hlint chosen extensions and GHC chosen extensions? In other words, can it be shown that the if hlint chose the same default extensions as GHC, it'd fail to parse x% of files?

i don't know about that.

shayne-fletcher avatar Jul 18 '22 01:07 shayne-fletcher

Now that we have established this ticket has a valid point, I’ve updated the title to better suit the problem.

asarkar avatar Jul 18 '22 02:07 asarkar

GHC gets extensions from .cabal files, on the command line, in stack.yaml files, and in the source file itself, plus those it defaults to. Of those, HLint can readily see those in the source file and which GHC defaults to, but not the others. That means that to "do the same as GHC" we can either try and find all the other sources (hard, maybe impossible) or just turn on most that don't break too much (easy). Note that where we can match GHC (e.g. through Haskell Language Server) that's what we do. If Cabal could run HLint, that would also be a place where it should match the right flags. But given just the command line hlint tool, there's no reasonable way to just match GHC.

ndmitchell avatar Jul 18 '22 18:07 ndmitchell

turn on most

There’s a definitive list of the extensions enabled by default by GHC. What this ticket is asking is for hlint to use that list instead of making up its own. Is that what you referred to as hard, almost impossible?

asarkar avatar Jul 18 '22 18:07 asarkar

No, the list of extensions enabled by GHC in your project is based on those defined in Cabal, Stack, command line flags and elsewhere. We can get the list of extensions enabled if you type ghc, but since that doesn't work in most projects, it's not that helpful. What we want is to approximate the extensions enabled in your project.

ndmitchell avatar Jul 18 '22 18:07 ndmitchell

I’ve none defined. I think that’s the only use case we are concerned about here. When the user does specify, that’s a different use case and if I understand correctly, isn’t handled currently. Setting the default list same as GHC would fix the first use case, and not make the second worse. It seems to be an improvement.

asarkar avatar Jul 18 '22 19:07 asarkar

I'm not sure what you mean. Consider the case that Steve has a project. In his .cabal file he has the extension PatternSynonyms enabled. Currently HLint works. If I follow the same default as GHC, it would not. Audrey has a project, he uses a custom build system like Bazel, and in herproject he has TemplateHaskell turned on in the .bazelconfig that applies to every project in her megarepo. Currently HLint works, but with the same default as GHC, it doesn't. My understanding based on talking to users is that if HLint defaulted to what GHC does (which you can do with a .hlint.yaml file if you want), then fewer projects would work out of the box.

ndmitchell avatar Jul 18 '22 21:07 ndmitchell

In his .cabal file he has the extension PatternSynonyms enabled. Currently HLint works. If I follow the same default as GHC, it would not

If user has enabled any extensions, then hlint won't fallback to defaults. All I'm saying is that when the user hasn't enabled any extensions, GHC defaults and hlint defaults aren't the same.

asarkar avatar Jul 19 '22 06:07 asarkar

I don't follow. In the .cabal file the user has enabled PatternSynonyms. What should HLint do?

If user has enabled any extensions, then hlint won't fallback to defaults

How do we detect if a user has enabled any extensions in their .cabal/stack/bazel projects?

ndmitchell avatar Jul 19 '22 20:07 ndmitchell