haskell / haskell-language-server

Official haskell ide support via language server (LSP). Successor of ghcide & haskell-ide-engine.
Apache License 2.0
2.71k stars 367 forks source link

Hlint suggestion produces invalid code (breaks block identation) (revisit after mpickering/apply-refact#95) #593

Closed emlautarom1 closed 9 months ago

emlautarom1 commented 3 years ago

Your environment

Output of haskell-language-server --probe-tools or haskell-language-server-wrapper --probe-tools:

haskell-language-server version: 0.6.0.0 (GHC: 8.8.4) (PATH: /home/emlautarom1/.config/Code/User/globalStorage/haskell.haskell/haskell-language-server-0.6.0-linux-8.8.4) (GIT hash: 372a12e797069dc3ac4fa33dcaabe3b992999d7c)
Tool versions found on the $PATH
cabal:      3.2.0.0
stack:      Not found
ghc:        8.8.4

Which lsp-client do you use: VSCode Describe your project (alternative: link to the project): Single .hs file Contents of hie.yaml: I'm using implicit cradle (no hie.yaml)

Steps to reproduce

  1. Define a function like the following:
    -- The body of f was generated using tactics (case split, introduce lambda, etc)
    f :: Bool -> Integer
    f = (\ b ->  (case b of
    False -> 1
    True -> 0))
  2. Apply the refactors suggested by hlint. The resulting code is:
    f :: Bool -> Integer
    f b = case b of
    False -> 1
    True -> 0

Expected behaviour

Applying hints should produce working code. The CLI of hlint produces the correct output (it compiles):

Factorial.hs:(2,1)-(4,14): Warning: Redundant lambda
Found:
  f = (\ b
         -> (case b of
               False -> 1
               True -> 0))
Perhaps:
  f b
    = case b of
        False -> 1
        True -> 0

Factorial.hs:(2,5)-(4,14): Suggestion: Redundant bracket
Found:
  (\ b
     -> (case b of
           False -> 1
           True -> 0))
Perhaps:
  \ b
    -> (case b of
          False -> 1
          True -> 0)

Factorial.hs:(2,15)-(4,12): Suggestion: Use if
Found:
  case b of
    False -> 1
    True -> 0
Perhaps:
  if b then 0 else 1

3 hints

Actual behaviour

The result of applying the suggestion produces code that does not compile anymore since there's an error on line 3: parse error on input ‘->’, since the False and True branches are missing whitespace at the beginning of the line.

Include debug information

Execute in the root of your project the command haskell-language-server --debug . and paste the logs here:

Debug output: ``` (haskell-language-server)Ghcide setup tester in /home/emlautarom1/.config/Code/User/globalStorage/haskell.haskell. Report bugs at https://github.com/haskell/haskell-language-server/issues Tool versions found on the $PATH cabal: 3.2.0.0 stack: Not found ghc: 8.8.4 Step 1/4: Finding files to test in /home/emlautarom1/.config/Code/User/globalStorage/haskell.haskell Found 1 files Step 2/4: Looking for hie.yaml files that control setup Found 1 cradle Step 3/4: Initializing the IDE Step 4/4: Type checking the files [INFO] Consulting the cradle for "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs" NotShowMessage (NotificationMessage {_jsonrpc = "2.0", _method = WindowShowMessage, _params = ShowMessageParams {_xtype = MtInfo, _message = "No [cradle](https://github.com/mpickering/hie-bios#hie-bios) found for /home/emlautarom1/Development/Haskell/Snippets/Factorial.hs.\n Proceeding with [implicit cradle](https://hackage.haskell.org/package/implicit-hie).\nYou should ignore this message, unless you see a 'Multi Cradle: No prefixes matched' error."}}) [INFO] Using interface files cache dir: /home/emlautarom1/.cache/ghcide/main-da39a3ee5e6b4b0d3255bfef95601890afd80709 [INFO] Making new HscEnv[main] [INFO] finish: User TypeCheck (took 0.02s) Completed (1 file worked, 0 files failed) ```

Paste the logs from the lsp-client, e.g. for VS Code

LSP logs: ``` 2020-11-12 12:07:42.210615514 [ThreadId 1319] - DocumentHighlight request at position 2:8 in file: /home/emlautarom1/Development/Haskell/Snippets/Factorial.hs 2020-11-12 12:07:42.460261317 [ThreadId 1326] - finish: CodeAction (took 0.00s) 2020-11-12 12:07:42.460435735 [ThreadId 1331] - finish: CodeAction:PackageExports (took 0.00s) 2020-11-12 12:07:42.460818634 [ThreadId 1332] - finish: importLens (took 0.00s) 2020-11-12 12:07:42.46101887 [ThreadId 1334] - finish: retrie (took 0.00s) 2020-11-12 12:07:42.461206202 [ThreadId 1336] - finish: tactic (took 0.00s) 2020-11-12 12:07:42.461402361 [ThreadId 1341] - finish: tactic (took 0.00s) 2020-11-12 12:07:42.461528588 [ThreadId 1342] - finish: tactic (took 0.00s) 2020-11-12 12:07:43.603960105 [ThreadId 1348] - finish: CodeAction (took 0.00s) 2020-11-12 12:07:43.6040479 [ThreadId 1350] - finish: CodeAction:PackageExports (took 0.00s) 2020-11-12 12:07:43.604295896 [ThreadId 1352] - finish: importLens (took 0.00s) 2020-11-12 12:07:43.604504568 [ThreadId 1354] - finish: retrie (took 0.00s) 2020-11-12 12:07:43.604726344 [ThreadId 1356] - finish: tactic (took 0.00s) 2020-11-12 12:07:43.60479846 [ThreadId 1358] - finish: tactic (took 0.00s) 2020-11-12 12:07:43.604908927 [ThreadId 1360] - finish: tactic (took 0.00s) 2020-11-12 12:07:44.755344189 [ThreadId 1366] - hlint:applyAllCmd:file=NormalizedFilePath "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs" 2020-11-12 12:07:44.755538103 [ThreadId 1367] - hlint:getIdeas:file:NormalizedFilePath "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs" 2020-11-12 12:07:44.755655884 [ThreadId 1367] - hlint:getIdeas:setExtensions:[Cpp,OverlappingInstances,UndecidableInstances,IncoherentInstances,UndecidableSuperClasses,MonomorphismRestriction,MonoPatBinds,MonoLocalBinds,RelaxedPolyRec,ExtendedDefaultRules,ForeignFunctionInterface,UnliftedFFITypes,InterruptibleFFI,CApiFFI,GHCForeignImportPrim,JavaScriptFFI,ParallelArrays,TemplateHaskell,TemplateHaskellQuotes,ImplicitParams,ImplicitPrelude,ScopedTypeVariables,AllowAmbiguousTypes,UnliftedNewtypes,BangPatterns,TypeFamilies,TypeFamilyDependencies,TypeInType,OverloadedStrings,OverloadedLists,NumDecimals,DisambiguateRecordFields,RecordWildCards,RecordPuns,ViewPatterns,GADTs,GADTSyntax,NPlusKPatterns,DoAndIfThenElse,BlockArguments,RebindableSyntax,ConstraintKinds,PolyKinds,DataKinds,InstanceSigs,ApplicativeDo,StandaloneDeriving,DeriveDataTypeable,AutoDeriveTypeable,DeriveFunctor,DeriveTraversable,DeriveFoldable,DeriveGeneric,DefaultSignatures,DeriveAnyClass,DeriveLift,DerivingStrategies,DerivingVia,TypeSynonymInstances,FlexibleContexts,FlexibleInstances,ConstrainedClassMethods,MultiParamTypeClasses,NullaryTypeClasses,FunctionalDependencies,UnicodeSyntax,ExistentialQuantification,MagicHash,EmptyDataDecls,KindSignatures,RoleAnnotations,ParallelListComp,MonadComprehensions,GeneralizedNewtypeDeriving,PostfixOperators,TupleSections,PatternGuards,LiberalTypeSynonyms,RankNTypes,ImpredicativeTypes,TypeOperators,ExplicitNamespaces,PackageImports,ExplicitForAll,AlternativeLayoutRuleTransitional,DatatypeContexts,NondecreasingIndentation,RelaxedLayout,TraditionalRecordSyntax,LambdaCase,MultiWayIf,BinaryLiterals,HexFloatLiterals,DuplicateRecordFields,OverloadedLabels,EmptyCase,PatternSynonyms,PartialTypeSignatures,NamedWildCards,TypeApplications,Strict,StrictData,MonadFailDesugaring,EmptyDataDeriving,NumericUnderscores,QuantifiedConstraints,ImportQualifiedPost,CUSKs,StandaloneKindSignatures,StarIsType] 2020-11-12 12:07:44.756260841 [ThreadId 1367] - finish: applyHint (took 0.00s) 2020-11-12 12:07:44.756262053 [ThreadId 1366] - applyHint:apply=[("/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs:(2,1)-(4,14): Warning: Redundant lambda\nFound:\n f = (\\ b\n -> (case b of\n False -> 1\n True -> 0))\nPerhaps:\n f b\n = case b of\n False -> 1\n True -> 0\n",[Replace {rtype = Decl, pos = SrcSpan {startLine = 2, startCol = 1, endLine = 4, endCol = 15}, subts = [("body",SrcSpan {startLine = 2, startCol = 15, endLine = 4, endCol = 13}),("a",SrcSpan {startLine = 2, startCol = 8, endLine = 2, endCol = 9})], orig = "f a = body"}]),("/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs:(2,5)-(4,14): Suggestion: Redundant bracket\nFound:\n (\\ b\n -> (case b of\n False -> 1\n True -> 0))\nPerhaps:\n \\ b\n -> (case b of\n False -> 1\n True -> 0)\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 2, startCol = 5, endLine = 4, endCol = 15}, subts = [("x",SrcSpan {startLine = 2, startCol = 6, endLine = 4, endCol = 14})], orig = "x"}]),("/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs:(2,15)-(4,12): Suggestion: Use if\nFound:\n case b of\n False -> 1\n True -> 0\nPerhaps:\n if b then 0 else 1\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 2, startCol = 15, endLine = 4, endCol = 13}, subts = [("a",SrcSpan {startLine = 2, startCol = 20, endLine = 2, endCol = 21}),("f",SrcSpan {startLine = 3, startCol = 13, endLine = 3, endCol = 14}),("t",SrcSpan {startLine = 4, startCol = 12, endLine = 4, endCol = 13})], orig = "if a then t else f"}])] 2020-11-12 12:07:44.759357908 [ThreadId 1369] - finish: hlint (took 0.00s) 2020-11-12 12:07:44.763807536 [ThreadId 1366] - hlint:applyHint:diff=WorkspaceEdit {_changes = Nothing, _documentChanges = Just (List [TextDocumentEdit {_textDocument = VersionedTextDocumentIdentifier {_uri = Uri {getUri = "file:///home/emlautarom1/Development/Haskell/Snippets/Factorial.hs"}, _version = Just 0}, _edits = List [TextEdit {_range = Range {_start = Position {_line = 1, _character = 0}, _end = Position {_line = 3, _character = 14}}, _newText = "f b = case b of\nFalse -> 1\nTrue -> 0"}]}])} 2020-11-12 12:07:44.769548088 [ThreadId 1366] - hlint:applyAllCmd:res=Right (WorkspaceEdit {_changes = Nothing, _documentChanges = Just (List [TextDocumentEdit {_textDocument = VersionedTextDocumentIdentifier {_uri = Uri {getUri = "file:///home/emlautarom1/Development/Haskell/Snippets/Factorial.hs"}, _version = Just 0}, _edits = List [TextEdit {_range = Range {_start = Position {_line = 1, _character = 0}, _end = Position {_line = 3, _character = 14}}, _newText = "f b = case b of\nFalse -> 1\nTrue -> 0"}]}])}) 2020-11-12 12:07:44.780002585 [ThreadId 1375] - finish: CodeAction (took 0.00s) 2020-11-12 12:07:44.780112922 [ThreadId 1377] - finish: CodeAction:PackageExports (took 0.00s) 2020-11-12 12:07:44.780488317 [ThreadId 1379] - finish: importLens (took 0.00s) 2020-11-12 12:07:44.780748887 [ThreadId 1381] - finish: retrie (took 0.00s) 2020-11-12 12:07:44.780965634 [ThreadId 1383] - finish: tactic (took 0.00s) 2020-11-12 12:07:44.781040625 [ThreadId 1385] - finish: tactic (took 0.00s) 2020-11-12 12:07:44.781099586 [ThreadId 1387] - finish: tactic (took 0.00s) 2020-11-12 12:07:44.790892931 [ThreadId 27] - Modified text document: file:///home/emlautarom1/Development/Haskell/Snippets/Factorial.hs 2020-11-12 12:07:44.792439295 [ThreadId 1446] - getClientConfigAction:clientSettings:Just (Object (fromList [("haskell",Object (fromList [("logFile",String ""),("updateBehavior",String "keep-up-to-date"),("hlintOn",Bool True),("formatOnImportOn",Bool True),("indentationRules",Object (fromList [("enabled",Bool True)])),("liquidOn",Bool False),("languageServerVariant",String "haskell-language-server"),("serverExecutablePath",String ""),("diagnosticsOnChange",Bool True),("completionSnippetsOn",Bool True),("maxNumberOfProblems",Number 100.0),("formattingProvider",String "ormolu"),("trace",Object (fromList [("server",String "off")]))]))])) 2020-11-12 12:07:44.792555503 [ThreadId 1446] - hlint:getIdeas:file:NormalizedFilePath "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs" 2020-11-12 12:07:45.033214908 [ThreadId 1457] - Plugin.makeCodeLens (ideLogger) 2020-11-12 12:07:45.033668811 [ThreadId 1458] - finish: codeLens (took 0.00s) 2020-11-12 12:07:45.034047933 [ThreadId 1463] - finish: (took 0.00s) 2020-11-12 12:07:45.034229744 [ThreadId 1464] - finish: ModuleName.ghcSession (took 0.00s) 2020-11-12 12:07:45.034591714 [ThreadId 1466] - finish: ModuleName.GetParsedModule (took 0.00s) 2020-11-12 12:07:45.035223381 [ThreadId 1471] - Plugin.makeCodeLens (ideLogger) 2020-11-12 12:07:45.035411003 [ThreadId 1472] - finish: codeLens (took 0.00s) 2020-11-12 12:07:45.035641205 [ThreadId 1474] - finish: (took 0.00s) 2020-11-12 12:07:45.035762173 [ThreadId 1476] - finish: ModuleName.ghcSession (took 0.00s) 2020-11-12 12:07:45.035955716 [ThreadId 1478] - finish: ModuleName.GetParsedModule (took 0.00s) 2020-11-12 12:07:45.048052247 [ThreadId 1484] - finish: CodeAction (took 0.00s) 2020-11-12 12:07:45.048233367 [ThreadId 1489] - finish: CodeAction:PackageExports (took 0.00s) 2020-11-12 12:07:45.048382488 [ThreadId 1490] - finish: importLens (took 0.00s) 2020-11-12 12:07:45.048519495 [ThreadId 1492] - finish: retrie (took 0.00s) 2020-11-12 12:07:45.048706145 [ThreadId 1494] - finish: tactic (took 0.00s) 2020-11-12 12:07:45.048875544 [ThreadId 1499] - finish: tactic (took 0.00s) 2020-11-12 12:07:45.048915799 [ThreadId 1500] - finish: tactic (took 0.00s) 2020-11-12 12:07:45.291323207 [ThreadId 27] - Saved text document: file:///home/emlautarom1/Development/Haskell/Snippets/Factorial.hs 2020-11-12 12:07:45.29272473 [ThreadId 1562] - Typechecking reverse dependencies forNormalizedFilePath "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs": [] 2020-11-12 12:07:45.292827553 [ThreadId 1566] - getClientConfigAction:clientSettings:Just (Object (fromList [("haskell",Object (fromList [("logFile",String ""),("updateBehavior",String "keep-up-to-date"),("hlintOn",Bool True),("formatOnImportOn",Bool True),("indentationRules",Object (fromList [("enabled",Bool True)])),("liquidOn",Bool False),("languageServerVariant",String "haskell-language-server"),("serverExecutablePath",String ""),("diagnosticsOnChange",Bool True),("completionSnippetsOn",Bool True),("maxNumberOfProblems",Number 100.0),("formattingProvider",String "ormolu"),("trace",Object (fromList [("server",String "off")]))]))])) 2020-11-12 12:07:45.292990799 [ThreadId 1566] - hlint:getIdeas:file:NormalizedFilePath "/home/emlautarom1/Development/Haskell/Snippets/Factorial.hs" ```
jneira commented 3 years ago

Hi @emlautarom1, thanks for the bug report. The error is reproduced by the plugin but too by the cli (hlint + apply-refact) in my tests:

PS D:\dev\ws\haskell\cabal-test> cat .\src\HlintTests.hs
module HlintTests where

g :: Bool -> Integer
g = (\ b ->  (case b of
   False -> 1
   True -> 0))

PS D:\dev\ws\haskell\cabal-test> hlint .\src\HlintTests.hs --refactor
module HlintTests where

g :: Bool -> Integer
g b = case b of
False -> 1
True -> 0

PS D:\dev\ws\haskell\cabal-test> hlint --version
HLint v3.2.1, (C) Neil Mitchell 2006-2020
PS D:\dev\ws\haskell\cabal-test> refactor --version
v0.8.2.1

So i am afraid that should be fixed upstream. @soiamsoNG has created already a test case: https://github.com/mpickering/apply-refact/pull/92/files

jneira commented 3 years ago

We reported the issue upstream and the temporary workaround will be not apply the refactoring if it would break identation until ghc-exactprint is fixed

michaelpj commented 9 months ago

Closing as it's just an upstream issue