11
11
{-# LANGUAGE TypeFamilies #-}
12
12
{-# LANGUAGE TypeOperators #-}
13
13
{-# LANGUAGE UndecidableInstances #-}
14
- -- See Note [Constraints]
15
- {-# OPTIONS_GHC -Wno-redundant-constraints #-}
16
14
17
15
module Ide.Plugin.Properties
18
16
( PropertyType (.. ),
@@ -44,13 +42,11 @@ import qualified Data.Aeson.Types as A
44
42
import Data.Either (fromRight )
45
43
import Data.Function ((&) )
46
44
import Data.Kind (Constraint , Type )
47
- import qualified Data.Map.Strict as Map
48
45
import Data.Proxy (Proxy (.. ))
49
46
import Data.String (IsString (fromString ))
50
47
import qualified Data.Text as T
51
48
import GHC.OverloadedLabels (IsLabel (.. ))
52
49
import GHC.TypeLits
53
- import Unsafe.Coerce (unsafeCoerce )
54
50
55
51
-- | Types properties may have
56
52
data PropertyType
@@ -114,7 +110,10 @@ data SomePropertyKeyWithMetaData
114
110
-- A property is an immediate child of the json object in each plugin's "config" section.
115
111
-- It was designed to be compatible with vscode's settings UI.
116
112
-- 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 '[]
118
117
119
118
-- | A proxy type in order to allow overloaded labels as properties' names at the call site
120
119
data KeyNameProxy (s :: Symbol ) = KnownSymbol s => KeyNameProxy
@@ -132,6 +131,10 @@ type family FindByKeyName (s :: Symbol) (r :: [PropertyKey]) :: PropertyType whe
132
131
FindByKeyName s ('PropertyKey s t ': _ ) = t
133
132
FindByKeyName s (_ ': xs ) = FindByKeyName s xs
134
133
134
+ type family IsPropertySymbol (s :: Symbol ) (r :: PropertyKey ) :: Bool where
135
+ IsPropertySymbol s ('PropertyKey s _ ) = 'True
136
+ IsPropertySymbol s _ = 'False
137
+
135
138
type family Elem (s :: Symbol ) (r :: [PropertyKey ]) :: Constraint where
136
139
Elem s ('PropertyKey s _ ': _ ) = ()
137
140
Elem s (_ ': xs ) = Elem s xs
@@ -143,7 +146,17 @@ type family NotElem (s :: Symbol) (r :: [PropertyKey]) :: Constraint where
143
146
NotElem s '[] = ()
144
147
145
148
-- | 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
147
160
148
161
-- ---------------------------------------------------------------------
149
162
@@ -164,7 +177,7 @@ type HasProperty s k t r = (k ~ 'PropertyKey s t, Elem s r, FindByKeyName s r ~
164
177
-- @
165
178
166
179
emptyProperties :: Properties '[]
167
- emptyProperties = Properties Map. empty
180
+ emptyProperties = EmptyProperties
168
181
169
182
insert ::
170
183
(k ~ 'PropertyKey s t , NotElem s r , KnownSymbol s ) =>
@@ -173,30 +186,14 @@ insert ::
173
186
MetaData t ->
174
187
Properties r ->
175
188
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
183
190
184
191
find ::
185
192
(HasProperty s k t r ) =>
186
193
KeyNameProxy s ->
187
194
Properties r ->
188
195
(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
200
197
201
198
-- ---------------------------------------------------------------------
202
199
@@ -350,7 +347,10 @@ defineEnumProperty kn description enums defaultValue =
350
347
351
348
-- | Converts a properties definition into kv pairs with default values from 'MetaData'
352
349
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
354
354
where
355
355
toEntry :: String -> SomePropertyKeyWithMetaData -> A. Pair
356
356
toEntry s = \ case
@@ -371,8 +371,10 @@ toDefaultJSON (Properties p) = [toEntry s v | (s, v) <- Map.toList p]
371
371
372
372
-- | Converts a properties definition into kv pairs as vscode schema
373
373
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
376
378
where
377
379
toEntry :: SomePropertyKeyWithMetaData -> A. Value
378
380
toEntry = \ case
0 commit comments