Skip to content

Commit 10b5f3b

Browse files
authored
Properties API: Remove unsafe coerce in favor of type class based method in (#3947)
* remove unsafe coerce to use type class based method * remove redundant-constraints suppresion
1 parent 4ae63f0 commit 10b5f3b

File tree

1 file changed

+30
-28
lines changed

1 file changed

+30
-28
lines changed

hls-plugin-api/src/Ide/Plugin/Properties.hs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
{-# LANGUAGE TypeFamilies #-}
1212
{-# LANGUAGE TypeOperators #-}
1313
{-# LANGUAGE UndecidableInstances #-}
14-
-- See Note [Constraints]
15-
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
1614

1715
module Ide.Plugin.Properties
1816
( PropertyType (..),
@@ -44,13 +42,11 @@ import qualified Data.Aeson.Types as A
4442
import Data.Either (fromRight)
4543
import Data.Function ((&))
4644
import Data.Kind (Constraint, Type)
47-
import qualified Data.Map.Strict as Map
4845
import Data.Proxy (Proxy (..))
4946
import Data.String (IsString (fromString))
5047
import qualified Data.Text as T
5148
import GHC.OverloadedLabels (IsLabel (..))
5249
import GHC.TypeLits
53-
import Unsafe.Coerce (unsafeCoerce)
5450

5551
-- | Types properties may have
5652
data PropertyType
@@ -114,7 +110,10 @@ data SomePropertyKeyWithMetaData
114110
-- A property is an immediate child of the json object in each plugin's "config" section.
115111
-- It was designed to be compatible with vscode's settings UI.
116112
-- Use 'emptyProperties' and 'useProperty' to create and consume 'Properties'.
117-
newtype Properties (r :: [PropertyKey]) = Properties (Map.Map String SomePropertyKeyWithMetaData)
113+
data Properties (r :: [PropertyKey]) where
114+
ConsProperties :: (k ~ 'PropertyKey s t, KnownSymbol s, NotElem s ks)
115+
=> KeyNameProxy s -> (SPropertyKey k) -> (MetaData t) -> Properties ks -> Properties (k : ks)
116+
EmptyProperties :: Properties '[]
118117

119118
-- | A proxy type in order to allow overloaded labels as properties' names at the call site
120119
data KeyNameProxy (s :: Symbol) = KnownSymbol s => KeyNameProxy
@@ -132,6 +131,10 @@ type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType whe
132131
FindByKeyName s ('PropertyKey s t ': _) = t
133132
FindByKeyName s (_ ': xs) = FindByKeyName s xs
134133

134+
type family IsPropertySymbol (s :: Symbol) (r :: PropertyKey) :: Bool where
135+
IsPropertySymbol s ('PropertyKey s _) = 'True
136+
IsPropertySymbol s _ = 'False
137+
135138
type family Elem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where
136139
Elem s ('PropertyKey s _ ': _) = ()
137140
Elem s (_ ': xs) = Elem s xs
@@ -143,7 +146,17 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where
143146
NotElem s '[] = ()
144147

145148
-- | In row @r@, there is a 'PropertyKey' @k@, which has name @s@ and carries haskell type @t@
146-
type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s)
149+
type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~ t, KnownSymbol s, FindPropertyMeta s r t)
150+
class FindPropertyMeta (s :: Symbol) (r :: [PropertyKey]) t where
151+
findSomePropertyKeyWithMetaData :: KeyNameProxy s -> Properties r -> (SPropertyKey ('PropertyKey s t), MetaData t)
152+
instance (FindPropertyMetaIf (IsPropertySymbol symbol k) symbol k ks t) => FindPropertyMeta symbol (k : ks) t where
153+
findSomePropertyKeyWithMetaData = findSomePropertyKeyWithMetaDataIf
154+
class (bool ~ IsPropertySymbol symbol k) => FindPropertyMetaIf bool symbol k ks t where
155+
findSomePropertyKeyWithMetaDataIf :: KeyNameProxy symbol -> Properties (k : ks) -> (SPropertyKey ('PropertyKey symbol t), MetaData t)
156+
instance (k ~ 'PropertyKey s t) => FindPropertyMetaIf 'True s k ks t where
157+
findSomePropertyKeyWithMetaDataIf _ (ConsProperties _ k m _) = (k, m)
158+
instance ('False ~ IsPropertySymbol s k, FindPropertyMeta s ks t) => FindPropertyMetaIf 'False s k ks t where
159+
findSomePropertyKeyWithMetaDataIf s (ConsProperties _ _ _ ks) = findSomePropertyKeyWithMetaData s ks
147160

148161
-- ---------------------------------------------------------------------
149162

@@ -164,7 +177,7 @@ type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~
164177
-- @
165178

166179
emptyProperties :: Properties '[]
167-
emptyProperties = Properties Map.empty
180+
emptyProperties = EmptyProperties
168181

169182
insert ::
170183
(k ~ 'PropertyKey s t, NotElem s r, KnownSymbol s) =>
@@ -173,30 +186,14 @@ insert ::
173186
MetaData t ->
174187
Properties r ->
175188
Properties (k ': r)
176-
insert kn key metadata (Properties old) =
177-
Properties
178-
( Map.insert
179-
(symbolVal kn)
180-
(SomePropertyKeyWithMetaData key metadata)
181-
old
182-
)
189+
insert = ConsProperties
183190

184191
find ::
185192
(HasProperty s k t r) =>
186193
KeyNameProxy s ->
187194
Properties r ->
188195
(SPropertyKey k, MetaData t)
189-
find kn (Properties p) = case p Map.! symbolVal kn of
190-
(SomePropertyKeyWithMetaData sing metadata) ->
191-
-- Note [Constraints]
192-
-- It's safe to use unsafeCoerce here:
193-
-- Since each property name is unique that the redefinition will be prevented by predication on the type level list,
194-
-- the value we get from the name-indexed map must be exactly the singleton and metadata corresponding to the type.
195-
-- We drop this information at type level: some of the above type families return '() :: Constraint',
196-
-- so GHC will consider them as redundant.
197-
-- But we encode it using semantically identical 'Map' at term level,
198-
-- which avoids inducting on the list by defining a new type class.
199-
unsafeCoerce (sing, metadata)
196+
find = findSomePropertyKeyWithMetaData
200197

201198
-- ---------------------------------------------------------------------
202199

@@ -350,7 +347,10 @@ defineEnumProperty kn description enums defaultValue =
350347

351348
-- | Converts a properties definition into kv pairs with default values from 'MetaData'
352349
toDefaultJSON :: Properties r -> [A.Pair]
353-
toDefaultJSON (Properties p) = [toEntry s v | (s, v) <- Map.toList p]
350+
toDefaultJSON pr = case pr of
351+
EmptyProperties -> []
352+
ConsProperties keyNameProxy k m xs ->
353+
toEntry (symbolVal keyNameProxy) (SomePropertyKeyWithMetaData k m) : toDefaultJSON xs
354354
where
355355
toEntry :: String -> SomePropertyKeyWithMetaData -> A.Pair
356356
toEntry s = \case
@@ -371,8 +371,10 @@ toDefaultJSON (Properties p) = [toEntry s v | (s, v) <- Map.toList p]
371371

372372
-- | Converts a properties definition into kv pairs as vscode schema
373373
toVSCodeExtensionSchema :: T.Text -> Properties r -> [A.Pair]
374-
toVSCodeExtensionSchema prefix (Properties p) =
375-
[fromString (T.unpack prefix <> k) A..= toEntry v | (k, v) <- Map.toList p]
374+
toVSCodeExtensionSchema prefix ps = case ps of
375+
EmptyProperties -> []
376+
ConsProperties (keyNameProxy :: KeyNameProxy s) (k :: SPropertyKey k) (m :: MetaData t) xs ->
377+
fromString (T.unpack prefix <> symbolVal keyNameProxy) A..= toEntry (SomePropertyKeyWithMetaData k m) : toVSCodeExtensionSchema prefix xs
376378
where
377379
toEntry :: SomePropertyKeyWithMetaData -> A.Value
378380
toEntry = \case

0 commit comments

Comments
 (0)