ndmitchell / hlint

Haskell source code suggestions
Other
1.46k stars 195 forks source link

Error on compiling code - parse error on input ``' #1059

Open freeman42x opened 4 years ago

freeman42x commented 4 years ago

Following code which compiles and runs:

main16=interact$p16
p16 s=[last$printf"%b"$(`uncurry`(read&&&(DTIR.hexDigitToInt.last.printf"%08x".(\z->read z::Int))$s))(*)]::String
-- Given a number N, multiply it by the last character of its hexadecimal form, then print the last digit of its binary representation.
-- 142359
-- 1

throws the following parse error:

/tmp/haskell-lsp48315/Archive.hs-00006-6155376213198126836.hs:49:25: error:
    parse error on input ``'
  main16=interact$p16
> p16 s=[last$printf"%b"$(`uncurry`(read&&&(DTIR.hexDigitToInt.last.printf"%08x".(\z->read z::Int))$s))(*)]::String
  -- Given a number N, multiply it by the last character of its hexadecimal form, then print the last digit of its binary representation.
  -- 142359
shayne-fletcher commented 4 years ago

There is a language extension default enabled that is affecting parsing. The question is which one.

shayne-fletcher commented 4 years ago

So, HLint default enables TemplateHaskell and I believe that affects the parse so that $( is considered a template Haskell token. I believe there are two solutions (1) write {-# LANGUAGE NoTemplateHaskell #-} or (2) add whitespace between the $ and the (.

ndmitchell commented 4 years ago

Passing -XNoTemplateHaskell on the command line or in the .hlint.yaml file (see the README) works too to disable parsing. I guess this is obfuscated competition in some way?

freeman42x commented 4 years ago

@ndmitchell This is from Codingame Clash of Code in the shortest mode. The goal is not obfuscation but getting the code that gives correct results with as few characters as possible.

ndmitchell commented 4 years ago

Thanks for the context @razvan-flavius-panda. So adding -XNoTemplateHaskell should be enough.

But I wonder if disabling TemplateHaskell by default might be reasonable? When we were on HSE, we often had to disable/enable lots of languages. Since moving to GHC, TemplateHaskell has come up, but everything else has been things where the author definitely should fix the code.

shayne-fletcher commented 4 years ago

Maybe. It does seem to be coming up somewhat often recently.

shayne-fletcher commented 4 years ago

On the other hand, there is no escaping the fact that whitespace sensitive operator lexing is becoming more prevalent in modern Haskell (think !, @ etc.). With this in mind and in this particular instance, despite the desire to minimize chars one might make a fair argument for requiring disambiguation of the token $( from the sequence $, (.

shayne-fletcher commented 4 years ago

I guess that argument would be, "stable in the presence of language extensions".

ndmitchell commented 4 years ago

For this case, -XNoTemplateHaskell is the right choice. However, a character-constrained program is a very rare special case, so I think you're right the general advice is probably to add enough spaces to not clash.

shayne-fletcher commented 4 years ago

For this case, -XNoTemplateHaskell is the right choice. However, a character-constrained program is a very rare special case, so I think you're right the general advice is probably to add enough spaces to not clash.

I do agree -XNoTemplateHaskell here is the way to go.

jmickelin commented 3 years ago

A suggestion: Would it perhaps be a possible to add a way to enable/disable specific extensions hlint loads inside .hlint.yaml on a per-module basis? Something like

- use-extensions:
  - name: NoPatternSynonyms
    within: My.Module.That.Got.A.Syntax.Error
  - name: NoTemplateHaskell
    within: Some.Other.Module

I ran into a similar issue with PattternSynonyms in code that uses pattern as a variable name, and adding a language pragma to explicitly disable it inside of the module seems like I'm doing it at the wrong level of concern. Since it's just for the sake of placating the linter, it should go in the linter configuration, in my opinion. We do use pattern synonyms elsewhere in the project, so I can't pass -XNoPatternSynonyms to hlint as a global option.

ndmitchell commented 3 years ago

Given that you don't have a problem in this module, I imagine the modules you do use PatternSynonyms must have explicitly turned it on? If so, disable pattern synonyms globally in HLint, and then HLint will still honour the module-level pragmas overriding the global one. That should work universally in all cases, as all you are doing is reflecting the .cabal settings in the .hlint.yaml (albeit with different defaults for some extensions).

Personally though, you have a project where some modules use pattern as a variable and some use it as a keyword. I'd remove all uses of pattern as a variable for human comprehension, so its consistent.

jmickelin commented 3 years ago

Oh yes, you are right. Disabling them globally in HLint is what I ended up doing, and I think it makes sense in this instance.

One complicating factor that I forgot to mention is that we are using a multi-cabal setup with a cabal.project file in a monorepo with many different packages, some of which with diverging sets of globally enabled extensions that have grown organically over time. We should probably enforce a repo-wide set of extensions using the global options available in the cabal.project file, but we aren't quite there just yet.