Skip to content

Commit 619788f

Browse files
EggBaconAndSpammergify[bot]
authored andcommitted
dhall-lsp-server: Fix hovering in presence of nested lets (#1537)
* Fix hacked-together parsers Back when we changed the linter to preserve let comments we made whitespace parsing explicit (previously combinators swallowed any trailing whitespace), but we forgot to update the hacked-together parsers used by the LSP server. As a result, various convenience features broke, but that's easy enough to fix. * Fix 'annotate lets' and 'type on hover' features Both features only work as intended if as much of the Dhall code as possible is wrapped in Note annotations, since we use that to figure out where the user was pointing. Since the removal of explicit multi-lets in the syntax the parser no longer wraps immediately nested lets (i.e. multilets) in Notes, which means we need to split them manually (like we used to). * Fix hovering test Now the behaviour expected by the test matches what we would want in reality.
1 parent 6c68f82 commit 619788f

File tree

3 files changed

+40
-12
lines changed

3 files changed

+40
-12
lines changed

dhall-lsp-server/src/Dhall/LSP/Backend/Parsing.hs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import Control.Applicative (optional, (<|>))
2424
import qualified Text.Megaparsec as Megaparsec
2525
import Text.Megaparsec (SourcePos(..))
2626

27-
2827
-- | Parse the outermost binding in a Src descriptor of a let-block and return
2928
-- the rest. Ex. on input `let a = 0 let b = a in b` parses `let a = 0 ` and
3029
-- returns the Src descriptor containing `let b = a in b`.
@@ -33,13 +32,20 @@ getLetInner (Src left _ text) = Megaparsec.parseMaybe (unParser parseLetInnerOff
3332
where parseLetInnerOffset = do
3433
setSourcePos left
3534
_let
35+
nonemptyWhitespace
3636
_ <- label
37+
whitespace
3738
_ <- optional (do
3839
_ <- _colon
39-
expr)
40+
nonemptyWhitespace
41+
_ <- expr
42+
whitespace)
4043
_equal
44+
whitespace
4145
_ <- expr
46+
whitespace
4247
_ <- optional _in
48+
whitespace
4349
begin <- getSourcePos
4450
tokens <- Megaparsec.takeRest
4551
end <- getSourcePos
@@ -53,11 +59,15 @@ getLetAnnot (Src left _ text) = Megaparsec.parseMaybe (unParser parseLetAnnot) t
5359
where parseLetAnnot = do
5460
setSourcePos left
5561
_let
62+
nonemptyWhitespace
5663
_ <- label
64+
whitespace
5765
begin <- getSourcePos
5866
(tokens, _) <- Megaparsec.match $ optional (do
5967
_ <- _colon
60-
expr)
68+
nonemptyWhitespace
69+
_ <- expr
70+
whitespace)
6171
end <- getSourcePos
6272
_ <- Megaparsec.takeRest
6373
return (Src begin end tokens)
@@ -73,7 +83,7 @@ getLetIdentifier src@(Src left _ text) =
7383
where parseLetIdentifier = do
7484
setSourcePos left
7585
_let
76-
whitespace
86+
nonemptyWhitespace
7787
begin <- getSourcePos
7888
(tokens, _) <- Megaparsec.match label
7989
end <- getSourcePos

dhall-lsp-server/src/Dhall/LSP/Backend/Typing.hs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Control.Applicative ((<|>))
1010
import Data.Bifunctor (first)
1111
import Data.Void (absurd, Void)
1212

13-
import Dhall.LSP.Backend.Parsing (getLetAnnot, getLetIdentifier,
13+
import Dhall.LSP.Backend.Parsing (getLetInner, getLetAnnot, getLetIdentifier,
1414
getLamIdentifier, getForallIdentifier)
1515
import Dhall.LSP.Backend.Diagnostics (Position, Range(..), rangeFromDhall)
1616
import Dhall.LSP.Backend.Dhall (WellTyped, fromWellTyped)
@@ -20,7 +20,10 @@ import Dhall.LSP.Backend.Dhall (WellTyped, fromWellTyped)
2020
-- that subexpression if possible.
2121
typeAt :: Position -> WellTyped -> Either String (Maybe Src, Expr Src Void)
2222
typeAt pos expr = do
23-
let expr' = fromWellTyped expr
23+
expr' <- case splitMultiLetSrc (fromWellTyped expr) of
24+
Just e -> return e
25+
Nothing -> Left "The impossible happened: failed to split let\
26+
\ blocks when preprocessing for typeAt'."
2427
(mSrc, typ) <- first show $ typeAt' pos empty expr'
2528
case mSrc of
2629
Just src -> return (Just src, normalize typ)
@@ -42,7 +45,6 @@ typeAt' pos _ctx (Note src (Pi _ _A _)) | Just src' <- getForallIdentifier src
4245
, pos `inside` src' =
4346
return (Just src', _A)
4447

45-
-- the input only contains singleton lets
4648
typeAt' pos ctx (Let (Binding { variable = x, value = a }) e@(Note src _)) | pos `inside` src = do
4749
_ <- typeWithA absurd ctx a
4850
let a' = shift 1 (V x 0) (normalize a)
@@ -72,12 +74,16 @@ typeAt' pos ctx expr = do
7274

7375
-- | Find the smallest Note-wrapped expression at the given position.
7476
exprAt :: Position -> Expr Src a -> Maybe (Expr Src a)
75-
exprAt pos e@(Note _ expr) = exprAt pos expr <|> Just e
76-
exprAt pos expr =
77+
exprAt pos e = do e' <- splitMultiLetSrc e
78+
exprAt' pos e'
79+
80+
exprAt' :: Position -> Expr Src a -> Maybe (Expr Src a)
81+
exprAt' pos e@(Note _ expr) = exprAt pos expr <|> Just e
82+
exprAt' pos expr =
7783
let subExprs = toListOf subExpressions expr
7884
in case [ (src, e) | (Note src e) <- subExprs, pos `inside` src ] of
7985
[] -> Nothing
80-
((src,e) : _) -> exprAt pos e <|> Just (Note src e)
86+
((src,e) : _) -> exprAt' pos e <|> Just (Note src e)
8187

8288

8389
-- | Find the smallest Src annotation containing the given position.
@@ -92,7 +98,12 @@ srcAt pos expr = do Note src _ <- exprAt pos expr
9298
-- textual error message.
9399
annotateLet :: Position -> WellTyped -> Either String (Src, Expr Src Void)
94100
annotateLet pos expr = do
95-
annotateLet' pos empty (fromWellTyped expr)
101+
expr' <- case splitMultiLetSrc (fromWellTyped expr) of
102+
Just e -> return e
103+
Nothing -> Left "The impossible happened: failed to split let\
104+
\ blocks when preprocessing for annotateLet'."
105+
annotateLet' pos empty expr'
106+
96107

97108
annotateLet' :: Position -> Context (Expr Src Void) -> Expr Src Void
98109
-> Either String (Src, Expr Src Void)
@@ -133,6 +144,13 @@ annotateLet' pos ctx expr = do
133144
(e:[]) -> annotateLet' pos ctx e
134145
_ -> Left "You weren't pointing at a let binder!"
135146

147+
-- Make sure all lets in a multilet are annotated with their source information
148+
splitMultiLetSrc :: Expr Src a -> Maybe (Expr Src a)
149+
splitMultiLetSrc (Note src (Let b (Let b' e))) = do
150+
src' <- getLetInner src
151+
splitMultiLetSrc (Note src (Let b (Note src' (Let b' e))))
152+
splitMultiLetSrc expr = subExpressions splitMultiLetSrc expr
153+
136154
-- Check if range lies completely inside a given subexpression.
137155
-- This version takes trailing whitespace into account
138156
-- (c.f. `sanitiseRange` from Backend.Diangostics).

dhall-lsp-server/tests/Main.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ hoveringSpec dir =
3737
case (extractContents typeHover, extractContents funcHover) of
3838
(HoverContents typeContent, HoverContents functionContent) -> do
3939
getValue typeContent `shouldBe` "Type"
40-
getValue functionContent `shouldBe` "{ home : Text, name : Text }"
40+
getValue functionContent `shouldBe` "\8704(_isAdmin : Bool) \8594 { home : Text, name : Text }"
4141
_ -> error "test failed"
4242
pure ()
4343

0 commit comments

Comments
 (0)