Skip to content

Commit 1f758b0

Browse files
authored
Merge pull request #39 from mlabs-haskell/compiler/multiple-declaration-error
Compiler: multiple declaration error
2 parents 419f3a9 + 47fe63b commit 1f758b0

File tree

7 files changed

+184
-89
lines changed

7 files changed

+184
-89
lines changed

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

Lines changed: 75 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.TyDef)) . 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,65 @@ 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.TyDef)) 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 ::
202+
forall effs.
203+
Member (State (M.Map Variable P.TyDef)) effs =>
204+
Member Err effs =>
205+
P.Module ->
206+
Eff effs Context
207+
module2Context m = do
208+
let typeDefinitions = m ^. #typeDefs
209+
ctxs <- runReader (m ^. #moduleName) $ do
210+
traverse (tyDef2Context (moduleName2ModName (m ^. #moduleName))) typeDefinitions
211+
pure $ mconcat ctxs
212+
213+
-- | Creates a Context entry from one type definition.
214+
tyDef2Context ::
215+
forall effs.
216+
Member (Reader P.ModuleName) effs =>
217+
Member (State (M.Map Variable P.TyDef)) effs =>
218+
Member Err effs =>
219+
ModName ->
220+
P.TyDef ->
221+
Eff effs Context
222+
tyDef2Context curModName tyDef = do
223+
r@(v, _) <- tyDef2NameAndKind curModName tyDef
224+
associateName v tyDef
225+
pure $ mempty & context .~ uncurry M.singleton r
226+
where
227+
-- Ads the name to our map - we can use its SourceLocation in the case of a
228+
-- double use. If it's already in our map - that means we've double declared it.
229+
associateName :: Variable -> P.TyDef -> Eff effs ()
230+
associateName v curTyDef = do
231+
modName <- ask
232+
maps <- get @(M.Map Variable P.TyDef)
233+
case maps M.!? v of
234+
Just otherTyDef ->
235+
throwError . PT.CompKindCheckError $ PT.MultipleTyDefError modName [otherTyDef, curTyDef]
236+
Nothing -> modify (M.insert v curTyDef)
237+
238+
{- | Converts the Proto Module name to a local modname - dropping the
239+
information.
240+
-}
220241
moduleName2ModName :: P.ModuleName -> ModName
221242
moduleName2ModName mName = (\p -> p ^. #name) <$> mName ^. #parts
222243

@@ -227,11 +248,6 @@ tyDef2NameAndKind curModName tyDef = do
227248
let k = tyAbsLHS2Kind (tyDef ^. #tyAbs)
228249
pure (name, k)
229250

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-
235251
tyAbsLHS2Kind :: P.TyAbs -> Kind
236252
tyAbsLHS2Kind tyAbs = foldWithArrow $ pKind2Type . (\x -> x ^. #argKind) <$> (tyAbs ^. #tyArgs)
237253

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,10 @@ instance IsMessage P.KindCheckError KindCheckError where
566566
<$> fromProto (err ^. P.tyName)
567567
<*> fromProto (err ^. P.inferredKind)
568568
<*> fromProto (err ^. P.definedKind)
569+
P.KindCheckError'MultipleTydefError err ->
570+
MultipleTyDefError
571+
<$> fromProto (err ^. P.moduleName)
572+
<*> traverse fromProto (err ^. P.tyDefs)
569573
Nothing -> throwProtoError EmptyField
570574

571575
toProto = \case
@@ -586,6 +590,10 @@ instance IsMessage P.KindCheckError KindCheckError where
586590
& (P.inconsistentTypeError . P.tyName) .~ toProto name
587591
& (P.inconsistentTypeError . P.inferredKind) .~ toProto ki
588592
& (P.inconsistentTypeError . P.definedKind) .~ toProto kd
593+
MultipleTyDefError m ds ->
594+
defMessage
595+
& (P.multipleTydefError . P.moduleName) .~ toProto m
596+
& (P.multipleTydefError . P.tyDefs) .~ (toProto <$> ds)
589597

590598
instance IsMessage P.CompilerError CompilerError where
591599
fromProto cErr = case cErr ^. P.maybe'compilerError of

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{-# LANGUAGE DuplicateRecordFields #-}
22
{-# OPTIONS_GHC -Wno-orphans #-}
33
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
4-
{-# OPTIONS_GHC -Wno-unused-imports #-}
4+
-- this is needed so the deriving via can generate Arbitrary instances for data
5+
-- definitions with more than 4 constructors
6+
{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
57

68
module LambdaBuffers.Compiler.ProtoCompat.Types (
79
ClassDef (..),
@@ -50,7 +52,7 @@ module LambdaBuffers.Compiler.ProtoCompat.Types (
5052

5153
-- for NonEmpty
5254
import Control.Exception (Exception)
53-
import Data.List.NonEmpty (NonEmpty ((:|)))
55+
import Data.List.NonEmpty (NonEmpty)
5456
import Data.Text (Text)
5557
import GHC.Generics (Generic)
5658
import LambdaBuffers.Compiler.KindCheck.Variable as VARS (Atom, Variable)
@@ -303,18 +305,16 @@ instance Arbitrary CompilerInput where
303305
fn n = CompilerInput <$> resize n arbitrary
304306

305307
data KindCheckError
306-
= -- | The following term is unbound in the following type definition.
307-
UnboundTermError TyName VarName
308-
| -- | Failed unifying TyRef with TyRef in TyName. This is the TyDef.
309-
IncorrectApplicationError TyName Kind Kind
310-
| -- | Kind recurses forever - not permitted.
311-
RecursiveKindError TyName
312-
| -- | The following type has the wrong.
313-
InconsistentTypeError TyName Kind Kind
308+
= UnboundTermError TyName VarName
309+
| IncorrectApplicationError TyName Kind Kind
310+
| RecursiveKindError TyName
311+
| InconsistentTypeError TyName Kind Kind
312+
| MultipleTyDefError ModuleName [TyDef]
314313
deriving stock (Show, Eq, Ord, Generic)
315314
deriving (Arbitrary) via GenericArbitrary KindCheckError
316315
instance Exception KindCheckError
317316

317+
-- | All the compiler errors.
318318
data CompilerError
319319
= CompKindCheckError KindCheckError
320320
| InternalError Text

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

Lines changed: 28 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,
@@ -28,15 +27,38 @@ import Test.Tasty (TestTree, testGroup)
2827
import Test.Tasty.HUnit (assertBool, testCase, (@?=))
2928
import Test.Tasty.QuickCheck (testProperty)
3029
import Test.Utils.CompilerInput (
30+
compilerInput'doubleDeclaration,
31+
compilerInput'doubleDeclarationDiffMod,
3132
compilerInput'incoherent,
3233
compilerInput'maybe,
3334
)
35+
import Test.Utils.Constructors (_ModuleName)
36+
import Test.Utils.TyDef (tyDef'maybe)
3437

3538
--------------------------------------------------------------------------------
3639
-- Top Level tests
3740

3841
test :: TestTree
39-
test = testGroup "Compiler tests" [testCheck, testFolds, testRefl]
42+
test = testGroup "Compiler tests" [testCheck, testFolds, testRefl, testMultipleDec]
43+
44+
--------------------------------------------------------------------------------
45+
-- Multiple declaration test
46+
47+
testMultipleDec :: TestTree
48+
testMultipleDec = testGroup "Multiple declaration tests." [doubleDeclaration, passingDoubleDeclaration]
49+
50+
doubleDeclaration :: TestTree
51+
doubleDeclaration =
52+
testCase "Two declarations of Maybe within the same module are caught." $
53+
check_ compilerInput'doubleDeclaration
54+
@?= Left (P.CompKindCheckError (P.MultipleTyDefError moduleName [tyDef'maybe, tyDef'maybe]))
55+
where
56+
moduleName = _ModuleName ["Module"]
57+
58+
passingDoubleDeclaration :: TestTree
59+
passingDoubleDeclaration =
60+
testCase "Two declarations of Maybe within different modules are fine." $
61+
check_ compilerInput'doubleDeclarationDiffMod @?= Right ()
4062

4163
--------------------------------------------------------------------------------
4264
-- Module tests
@@ -78,8 +100,8 @@ kcTestOrdering =
78100
shuffledMods <- shuffle mods
79101
pure (P.CompilerInput mods, P.CompilerInput shuffledMods)
80102

81-
eitherFailOrPass :: forall {a} {c}. Either a c -> Either () ()
82-
eitherFailOrPass = bimap (const ()) (const ())
103+
eitherFailOrPass :: forall {a} {c}. Either a c -> Either () ()
104+
eitherFailOrPass = bimap (const ()) (const ())
83105

84106
--------------------------------------------------------------------------------
85107
-- Fold tests
Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
module Test.Utils.CompilerInput (compilerInput'incoherent, compilerInput'maybe) where
1+
module Test.Utils.CompilerInput (compilerInput'incoherent, compilerInput'maybe, compilerInput'doubleDeclarationDiffMod, compilerInput'doubleDeclaration) where
22

3-
import Control.Lens ((&), (.~))
3+
import Control.Lens ((%~), (&), (.~))
44
import LambdaBuffers.Compiler.ProtoCompat qualified as P
55
import Test.Utils.Module (module'incoherent, module'maybe)
6+
import Test.Utils.SourceInfo (sourceInfo'empty)
7+
8+
_CompilerInput :: [P.Module] -> P.CompilerInput
9+
_CompilerInput x = P.CompilerInput {P.modules = x}
610

711
-- | Compiler Input containing 1 module with 1 definition - Maybe.
812
compilerInput'maybe :: P.CompilerInput
9-
compilerInput'maybe = P.CompilerInput {P.modules = [module'maybe]}
13+
compilerInput'maybe = _CompilerInput [module'maybe]
1014

1115
-- | Contains 2 definitions - 1 wrong one.
1216
compilerInput'incoherent :: P.CompilerInput
1317
compilerInput'incoherent = compilerInput'maybe & #modules .~ [module'incoherent]
18+
19+
-- | Declares maybe twice.
20+
compilerInput'doubleDeclaration :: P.CompilerInput
21+
compilerInput'doubleDeclaration = compilerInput'maybe <> compilerInput'maybe
22+
23+
-- | Declares maybe twice - in different modules
24+
compilerInput'doubleDeclarationDiffMod :: P.CompilerInput
25+
compilerInput'doubleDeclarationDiffMod =
26+
compilerInput'maybe
27+
<> _CompilerInput
28+
[ module'maybe & #moduleName . #parts %~ (P.ModuleNamePart "Module" sourceInfo'empty :)
29+
]

0 commit comments

Comments
 (0)