Skip to content

Commit c9c5ffc

Browse files
committed
Fix decoding of |?
1 parent da0bf00 commit c9c5ffc

File tree

4 files changed

+43
-11
lines changed

4 files changed

+43
-11
lines changed

lsp-types/lsp-types.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ library
8585
, some
8686
, template-haskell
8787
, text
88+
-- needed for aeson < 1
89+
, unordered-containers
8890

8991
if flag(force-ospath)
9092
build-depends: filepath ^>=1.4.100.0

lsp-types/src/Language/LSP/Protocol/Message/Types.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ instance FromJSON ResponseError where
8181
<*> v .:? "data"
8282
in fmap go . errorCode
8383
where go :: ResponseError -> ResponseError
84-
go x@(ResponseError (InR (ErrorCodes_Custom n)) _ _) =
85-
x{_code = InL (fromOpenEnumBaseType n)}
84+
go x@(ResponseError (InL (LSPErrorCodes_Custom n)) _ _) =
85+
x{_code = InR (fromOpenEnumBaseType n)}
8686
go x = x
8787

8888
-- | Response message type as defined in the spec.
@@ -149,8 +149,8 @@ instance (FromJSON (ErrorData m)) => FromJSON (TResponseError m) where
149149
<*> v .:? "data"
150150
in fmap go . errorCode
151151
where go :: TResponseError m -> TResponseError m
152-
go x@(TResponseError (InR (ErrorCodes_Custom n)) _ _) =
153-
x{_code = InL (fromOpenEnumBaseType n)}
152+
go x@(TResponseError (InL (LSPErrorCodes_Custom n)) _ _) =
153+
x{_code = InR (fromOpenEnumBaseType n)}
154154
go x = x
155155
instance (ToJSON (ErrorData m)) => ToJSON (TResponseError m) where
156156
toJSON = genericToJSON lspOptions

lsp-types/src/Language/LSP/Protocol/Types/Common.hs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE CPP #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE DeriveAnyClass #-}
34
{-# LANGUAGE DeriveGeneric #-}
@@ -21,12 +22,17 @@ module Language.LSP.Protocol.Types.Common (
2122
, (.=?)
2223
) where
2324

24-
import Control.Applicative
2525
import Control.DeepSeq
2626
import Control.Lens
2727
import Data.Aeson hiding (Null)
2828
import qualified Data.Aeson as J
29+
#if MIN_VERSION_aeson(2,0,0)
30+
import qualified Data.Aeson.KeyMap as KM
31+
#else
32+
import qualified Data.HashMap.Strict as KM
33+
#endif
2934
import Data.Hashable
35+
import Data.Set as Set
3036
import Data.String (fromString)
3137
import Data.Int (Int32)
3238
import Data.Mod.Word
@@ -93,11 +99,35 @@ instance (ToJSON a, ToJSON b) => ToJSON (a |? b) where
9399
toJSON (InL x) = toJSON x
94100
toJSON (InR x) = toJSON x
95101

96-
instance (FromJSON a, FromJSON b) => FromJSON (a |? b) where
97-
-- Important: Try to parse the **rightmost** type first, as in the specification
98-
-- the more complex types tend to appear on the right of the |, e.g.
99-
-- @colorProvider?: boolean | DocumentColorOptions | DocumentColorRegistrationOptions;@
100-
parseJSON v = InR <$> parseJSON v <|> InL <$> parseJSON v
102+
instance (FromJSON a, ToJSON a, FromJSON b, ToJSON b) => FromJSON (a |? b) where
103+
-- Truly atrocious and abominable hack. The issue is tha we may have siutations
104+
-- where some input JSON can parse correctly as both sides of the union, because
105+
-- we have no tag. What do we do in this situation? It's very unclear, and the
106+
-- spec is no help. The heuristic we adopt here is that it is better to take
107+
-- the version with "more fields". How do we work that out? By converting back
108+
-- to JSON and looking at the object fields.
109+
--
110+
-- Possibly we could do better by relying on Generic instances for a and b
111+
-- in order to work out which has more fields on the Haskell side.
112+
parseJSON v = do
113+
let ra :: Result a = fromJSON v
114+
rb :: Result b = fromJSON v
115+
case (ra, rb) of
116+
(Success a, Error _) -> pure $ InL a
117+
(Error _, Success b) -> pure $ InR b
118+
(Error e, Error _) -> fail e
119+
(Success a, Success b) -> case (toJSON a, toJSON b) of
120+
-- Both sides encode to the same thing, just pick one arbitrarily
121+
(l, r) | l == r -> pure $ InL a
122+
(Object oa, Object ob) ->
123+
let ka = Set.fromList $ KM.keys oa
124+
kb = Set.fromList $ KM.keys ob
125+
in if kb `Set.isSubsetOf` ka
126+
then pure $ InL a
127+
else if ka `Set.isSubsetOf` kb
128+
then pure $ InR b
129+
else fail $ "Could not decide which type of value to produce, left encodes to an object with keys: " ++ show ka ++ "; right has keys " ++ show kb
130+
(l, r) -> fail $ "Could not decide which type of value to produce, left encodes to: " ++ show l ++ "; right encodes to: " ++ show r
101131

102132
-- We could use 'Proxy' for this, as aeson also serializes it to/from null,
103133
-- but this is more explicit.

lsp-types/test/JsonSpec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ responseMessageSpec = do
6767
it "decodes result = null" $ do
6868
let input = "{\"jsonrpc\": \"2.0\", \"id\": 123, \"result\": null}"
6969
in J.decode input `shouldBe` Just
70-
((TResponseMessage "2.0" (Just (IdInt 123)) (Right $ InR Null)) :: TResponseMessage 'Method_WorkspaceExecuteCommand)
70+
((TResponseMessage "2.0" (Just (IdInt 123)) (Right $ InL J.Null)) :: TResponseMessage 'Method_WorkspaceExecuteCommand)
7171
it "handles missing params field" $ do
7272
J.eitherDecode "{ \"jsonrpc\": \"2.0\", \"id\": 15, \"method\": \"shutdown\"}"
7373
`shouldBe` Right (TRequestMessage "2.0" (IdInt 15) SMethod_Shutdown Nothing)

0 commit comments

Comments
 (0)