Skip to content

Commit f37af07

Browse files
committed
new: add multiple declaration check + test
1 parent 061ebf1 commit f37af07

File tree

4 files changed

+95
-66
lines changed

4 files changed

+95
-66
lines changed

lambda-buffers-compiler/src/LambdaBuffers/Compiler/KindCheck.hs

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
2+
13
module LambdaBuffers.Compiler.KindCheck (
24
-- * Kindchecking functions.
35
check,
@@ -11,9 +13,10 @@ module LambdaBuffers.Compiler.KindCheck (
1113

1214
import Control.Lens (view, (&), (.~), (^.))
1315
import Control.Monad (void)
14-
import Control.Monad.Freer (Eff, Members, reinterpret, run)
16+
import Control.Monad.Freer (Eff, Member, Members, interpret, reinterpret, run)
1517
import Control.Monad.Freer.Error (Error, runError, throwError)
1618
import Control.Monad.Freer.Reader (Reader, ask, runReader)
19+
import Control.Monad.Freer.State (State, evalState, get, modify)
1720
import Control.Monad.Freer.TH (makeEffect)
1821
import Data.Foldable (traverse_)
1922
import Data.List.NonEmpty (NonEmpty ((:|)), uncons, (<|))
@@ -36,51 +39,12 @@ import LambdaBuffers.Compiler.KindCheck.Inference qualified as I
3639
import LambdaBuffers.Compiler.KindCheck.Type (Type (App))
3740
import LambdaBuffers.Compiler.KindCheck.Variable (Variable (ForeignRef, LocalRef))
3841
import LambdaBuffers.Compiler.ProtoCompat (kind2ProtoKind)
39-
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P (
40-
ClassDef,
41-
CompilerError (..),
42-
CompilerInput,
43-
Constructor,
44-
Field,
45-
ForeignRef,
46-
InstanceClause,
47-
Kind,
48-
KindCheckError (
49-
InconsistentTypeError,
50-
IncorrectApplicationError,
51-
RecursiveKindError,
52-
UnboundTermError
53-
),
54-
KindRefType (KType),
55-
KindType (KindArrow, KindRef),
56-
LocalRef,
57-
Module,
58-
ModuleName,
59-
Product (..),
60-
Record,
61-
SourceInfo (SourceInfo),
62-
SourcePosition (SourcePosition),
63-
Sum,
64-
Tuple,
65-
Ty (..),
66-
TyAbs,
67-
TyApp,
68-
TyArg,
69-
TyBody (..),
70-
TyDef (TyDef),
71-
TyName,
72-
TyRef (..),
73-
TyVar,
74-
VarName (VarName),
75-
)
42+
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P
7643
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as PT
7744

7845
--------------------------------------------------------------------------------
7946
-- Types
8047

81-
-- FIXME(cstml) - We should add the following tests:
82-
-- - double declaration of a type
83-
8448
-- | Kind Check failure types.
8549
type CompilerErr = P.CompilerError
8650

@@ -121,20 +85,27 @@ makeEffect ''KindCheck
12185

12286
--------------------------------------------------------------------------------
12387

124-
runCheck :: Eff (Check ': '[]) a -> Either CompilerErr a
88+
-- | The Check effect runner.
89+
runCheck :: Eff '[Check, Err] a -> Either CompilerErr a
12590
runCheck = run . runError . runKindCheck . localStrategy . moduleStrategy . globalStrategy
12691

127-
-- | Run the check - return the validated context or the failure.
92+
{- | Run the check - return the validated context or the failure. The main API
93+
function of the library.
94+
-}
12895
check :: P.CompilerInput -> PT.CompilerOutput
12996
check = fmap (const PT.CompilerResult) . runCheck . kCheck
13097

131-
-- | Run the check - drop the result if it succeeds.
98+
-- | Run the check - drop the result if it succeeds - useful for testing.
13299
check_ :: P.CompilerInput -> Either CompilerErr ()
133100
check_ = void . runCheck . kCheck
134101

135102
--------------------------------------------------------------------------------
136103

137-
type Transform x y = forall effs {a}. Eff (x ': effs) a -> Eff (y ': effs) a
104+
{- | A transformation (in the context of the Kind Checker) is a mapping from one
105+
Effect to another. All effects can fial via the `Err` effect - which is
106+
essentially the Kind Check failure.
107+
-}
108+
type Transform x y = forall effs {a}. Member Err effs => Eff (x ': effs) a -> Eff (y ': effs) a
138109

139110
-- Transformation strategies
140111
globalStrategy :: Transform Check GlobalCheck
@@ -146,7 +117,7 @@ globalStrategy = reinterpret $ \case
146117

147118
moduleStrategy :: Transform GlobalCheck ModuleCheck
148119
moduleStrategy = reinterpret $ \case
149-
CreateContext ci -> resolveCreateContext ci
120+
CreateContext ci -> evalState (mempty @(M.Map Variable P.TyName)) . resolveCreateContext $ ci
150121
ValidateModule cx md -> do
151122
traverse_ (kCTypeDefinition (module2ModuleName md) cx) (md ^. #typeDefs)
152123
traverse_ (kCClassInstance cx) (md ^. #instances)
@@ -159,14 +130,14 @@ localStrategy = reinterpret $ \case
159130
KCClassInstance _ctx _instClause -> pure () -- "FIXME(cstml)"
160131
KCClass _ctx _classDef -> pure () -- "FIXME(cstml)"
161132

162-
runKindCheck :: Eff '[KindCheck] a -> Eff '[Err] a
163-
runKindCheck = reinterpret $ \case
133+
runKindCheck :: forall effs {a}. Member Err effs => Eff (KindCheck ': effs) a -> Eff effs a
134+
runKindCheck = interpret $ \case
164135
KindFromTyDef moduleName tydef -> runReader moduleName (tyDef2Type tydef)
165136
-- TyDefToTypes moduleName tydef -> runReader moduleName (tyDef2Types tydef)
166137
InferTypeKind _modName tyDef ctx ty -> either (handleErr tyDef) pure $ infer ctx ty
167138
CheckKindConsistency mname def ctx k -> runReader mname $ resolveKindConsistency def ctx k
168139
where
169-
handleErr :: forall a. P.TyDef -> InferErr -> Eff '[Err] a
140+
handleErr :: forall {b}. P.TyDef -> InferErr -> Eff effs b
170141
handleErr td = \case
171142
InferUnboundTermErr uA ->
172143
throwError . P.CompKindCheckError $ P.UnboundTermError (tyDef2TyName td) (var2VarName uA)
@@ -208,15 +179,50 @@ resolveKindConsistency tydef _ctx inferredKind = do
208179
throwError . P.CompKindCheckError $
209180
P.InconsistentTypeError n (kind2ProtoKind i) (kind2ProtoKind d)
210181

211-
resolveCreateContext :: forall effs. P.CompilerInput -> Eff effs Context
212-
resolveCreateContext ci = mconcat <$> traverse module2Context (ci ^. #modules)
213-
214182
tyDef2TyName :: P.TyDef -> P.TyName
215183
tyDef2TyName (P.TyDef n _ _) = n
216184

217-
module2Context :: forall effs. P.Module -> Eff effs Context
218-
module2Context m = mconcat <$> traverse (tyDef2Context (moduleName2ModName (m ^. #moduleName))) (m ^. #typeDefs)
185+
--------------------------------------------------------------------------------
186+
-- Context Creation
219187

188+
{- | Resolver function for the context creation - it fails if two identical
189+
declarations are found.
190+
-}
191+
resolveCreateContext ::
192+
forall effs.
193+
Member (State (M.Map Variable P.TyName)) effs =>
194+
Member Err effs =>
195+
P.CompilerInput ->
196+
Eff effs Context
197+
resolveCreateContext ci = do
198+
ctxs <- traverse module2Context (ci ^. #modules)
199+
pure $ mconcat ctxs
200+
201+
module2Context :: forall effs. Member (State (M.Map Variable P.TyName)) effs => Member Err effs => P.Module -> Eff effs Context
202+
module2Context m = do
203+
let typeDefinitions = m ^. #typeDefs
204+
ctxs <- traverse (tyDef2Context (moduleName2ModName (m ^. #moduleName))) typeDefinitions
205+
pure $ mconcat ctxs
206+
207+
-- | Creates a Context entry from one type definition.
208+
tyDef2Context :: forall effs. Member (State (M.Map Variable P.TyName)) effs => Member Err effs => ModName -> P.TyDef -> Eff effs Context
209+
tyDef2Context curModName tyDef@(P.TyDef tyName _ _) = do
210+
r@(v, _) <- tyDef2NameAndKind curModName tyDef
211+
associateName v tyName
212+
pure $ mempty & context .~ uncurry M.singleton r
213+
where
214+
-- Ads the name to our map - we can use its SourceLocation in the case of a
215+
-- double use. If it's already in our map - that means we've double declared it.
216+
associateName :: Variable -> P.TyName -> Eff effs ()
217+
associateName v t = do
218+
maps <- get @(M.Map Variable P.TyName)
219+
case maps M.!? v of
220+
Just otherTyName -> throwError . PT.CompReaderError $ PT.MultipleDeclaration otherTyName t
221+
Nothing -> modify (M.insert v t)
222+
223+
{- | Converts the Proto Module name to a local modname - dropping the
224+
information.
225+
-}
220226
moduleName2ModName :: P.ModuleName -> ModName
221227
moduleName2ModName mName = (\p -> p ^. #name) <$> mName ^. #parts
222228

@@ -227,11 +233,6 @@ tyDef2NameAndKind curModName tyDef = do
227233
let k = tyAbsLHS2Kind (tyDef ^. #tyAbs)
228234
pure (name, k)
229235

230-
tyDef2Context :: forall effs. ModName -> P.TyDef -> Eff effs Context
231-
tyDef2Context curModName tyDef = do
232-
r <- tyDef2NameAndKind curModName tyDef
233-
pure $ mempty & context .~ uncurry M.singleton r
234-
235236
tyAbsLHS2Kind :: P.TyAbs -> Kind
236237
tyAbsLHS2Kind tyAbs = foldWithArrow $ pKind2Type . (\x -> x ^. #argKind) <$> (tyAbs ^. #tyArgs)
237238

lambda-buffers-compiler/src/LambdaBuffers/Compiler/ProtoCompat/Types.hs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module LambdaBuffers.Compiler.ProtoCompat.Types (
4141
TyRef (..),
4242
TyVar (..),
4343
VarName (..),
44+
ReaderError (..),
4445
module VARS,
4546
) where
4647

@@ -289,8 +290,19 @@ data KindCheckError
289290
deriving (Arbitrary) via GenericArbitrary KindCheckError
290291
instance Exception KindCheckError
291292

293+
{- | Reader errors are validation errors which are not directly tied to the kind
294+
checking.
295+
-}
296+
data ReaderError
297+
= -- | The following type@(TyName) was declared here@(SourceInfo) and here@(SourceInfo).
298+
MultipleDeclaration TyName TyName
299+
deriving stock (Show, Eq, Ord, Generic)
300+
deriving (Arbitrary) via GenericArbitrary ReaderError
301+
302+
-- | All the compiler errors.
292303
data CompilerError
293304
= CompKindCheckError KindCheckError
305+
| CompReaderError ReaderError
294306
| InternalError Text
295307
deriving stock (Show, Eq, Ord, Generic)
296308
deriving (Arbitrary) via GenericArbitrary CompilerError

lambda-buffers-compiler/test/Test/KindCheck.hs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ import LambdaBuffers.Compiler.KindCheck.Type (Type (App, Var))
1212
import LambdaBuffers.Compiler.KindCheck.Variable (
1313
Variable (LocalRef),
1414
)
15-
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P (
16-
CompilerInput (CompilerInput),
17-
)
15+
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P
16+
1817
import Test.QuickCheck (
1918
Arbitrary (arbitrary, shrink),
2019
Property,
@@ -25,9 +24,11 @@ import Test.QuickCheck (
2524
(===),
2625
)
2726
import Test.Samples.Proto.CompilerInput (
27+
compilerInput'doubleDeclaration,
2828
compilerInput'incoherent,
2929
compilerInput'maybe,
3030
)
31+
import Test.Samples.Proto.Utils (_tyName)
3132
import Test.Tasty (TestTree, testGroup)
3233
import Test.Tasty.HUnit (assertBool, testCase, (@?=))
3334
import Test.Tasty.QuickCheck (testProperty)
@@ -36,7 +37,18 @@ import Test.Tasty.QuickCheck (testProperty)
3637
-- Top Level tests
3738

3839
test :: TestTree
39-
test = testGroup "Compiler tests" [testCheck, testFolds, testRefl]
40+
test = testGroup "Compiler tests" [testCheck, testFolds, testRefl, testMultipleDec]
41+
42+
--------------------------------------------------------------------------------
43+
-- Multiple declaration test
44+
45+
testMultipleDec :: TestTree
46+
testMultipleDec = testGroup "Multiple declaration tests." [doubleDeclaration]
47+
48+
doubleDeclaration :: TestTree
49+
doubleDeclaration =
50+
testCase "Two declarations of the same type was not detected." $
51+
check_ compilerInput'doubleDeclaration @?= Left (P.CompReaderError $ P.MultipleDeclaration (_tyName "Maybe") (_tyName "Maybe"))
4052

4153
--------------------------------------------------------------------------------
4254
-- Module tests
@@ -78,8 +90,8 @@ kcTestOrdering =
7890
shuffledMods <- shuffle mods
7991
pure (P.CompilerInput mods, P.CompilerInput shuffledMods)
8092

81-
eitherFailOrPass :: forall {a} {c}. Either a c -> Either () ()
82-
eitherFailOrPass = bimap (const ()) (const ())
93+
eitherFailOrPass :: forall {a} {c}. Either a c -> Either () ()
94+
eitherFailOrPass = bimap (const ()) (const ())
8395

8496
--------------------------------------------------------------------------------
8597
-- Fold tests

lambda-buffers-compiler/test/Test/Samples/Proto/CompilerInput.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Test.Samples.Proto.CompilerInput (compilerInput'incoherent, compilerInput'maybe) where
1+
module Test.Samples.Proto.CompilerInput (compilerInput'incoherent, compilerInput'maybe, compilerInput'doubleDeclaration) where
22

33
import Control.Lens ((&), (.~))
44
import LambdaBuffers.Compiler.ProtoCompat qualified as P
@@ -11,3 +11,7 @@ compilerInput'maybe = P.CompilerInput {P.modules = [module'maybe]}
1111
-- | Contains 2 definitions - 1 wrong one.
1212
compilerInput'incoherent :: P.CompilerInput
1313
compilerInput'incoherent = compilerInput'maybe & #modules .~ [module'incoherent]
14+
15+
-- | Declares maybe twice.
16+
compilerInput'doubleDeclaration :: P.CompilerInput
17+
compilerInput'doubleDeclaration = compilerInput'maybe <> compilerInput'maybe

0 commit comments

Comments
 (0)