1111{-# LANGUAGE TypeFamilies #-}
1212{-# LANGUAGE TypeOperators #-}
1313{-# LANGUAGE UndecidableInstances #-}
14- -- See Note [Constraints]
15- {-# OPTIONS_GHC -Wno-redundant-constraints #-}
1614
1715module Ide.Plugin.Properties
1816 ( PropertyType (.. ),
@@ -44,13 +42,11 @@ import qualified Data.Aeson.Types as A
4442import Data.Either (fromRight )
4543import Data.Function ((&) )
4644import Data.Kind (Constraint , Type )
47- import qualified Data.Map.Strict as Map
4845import Data.Proxy (Proxy (.. ))
4946import Data.String (IsString (fromString ))
5047import qualified Data.Text as T
5148import GHC.OverloadedLabels (IsLabel (.. ))
5249import GHC.TypeLits
53- import Unsafe.Coerce (unsafeCoerce )
5450
5551-- | Types properties may have
5652data 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
120119data 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+
135138type 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
166179emptyProperties :: Properties '[]
167- emptyProperties = Properties Map. empty
180+ emptyProperties = EmptyProperties
168181
169182insert ::
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
184191find ::
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'
352349toDefaultJSON :: 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
373373toVSCodeExtensionSchema :: 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