Skip to content

Commit b25f60e

Browse files
committed
Implemented solution to 'What do we do w/ the C a in instance C a => C (F a) puzzle, wrote tests for derive check, cleaned up modules
1 parent 7ca79f4 commit b25f60e

File tree

11 files changed

+559
-183
lines changed

11 files changed

+559
-183
lines changed

lambda-buffers-compiler/lambda-buffers-compiler.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ test-suite tests
153153
main-is: Test.hs
154154
build-depends:
155155
, containers
156+
, generic-lens
156157
, lambda-buffers-compiler
157158
, lambda-buffers-compiler-pb >=0.1
158-
, prettyprinter
159+
, lens
159160
, proto-lens >=0.7
160161
, QuickCheck >=2.14
161162
, tasty >=1.4

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Compat.hs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
{-# LANGUAGE OverloadedLabels #-}
22

3-
module LambdaBuffers.Compiler.TypeClass.Compat where
3+
module LambdaBuffers.Compiler.TypeClass.Compat (
4+
modulename,
5+
defToExp,
6+
tyToExp,
7+
appToExp,
8+
defToPat,
9+
tyToPat,
10+
appToPat,
11+
) where
412

513
import Control.Lens ((^.))
614
import Control.Lens.Combinators (view)
715
import Data.List.NonEmpty (NonEmpty ((:|)))
816
import Data.List.NonEmpty qualified as NE
917
import Data.Text qualified as T
1018
import LambdaBuffers.Compiler.ProtoCompat qualified as P
11-
import LambdaBuffers.Compiler.TypeClass.Pat
19+
import LambdaBuffers.Compiler.TypeClass.Pat (
20+
Exp (AppE, DecE, LitE, NilE, RefE),
21+
ExpressionLike (nil, (*:), (*=)),
22+
Literal (ModuleName, Name, Opaque, TyVar),
23+
Pat (AppP, DecP, LitP, NilP, RefP, VarP),
24+
toProdE,
25+
toProdP,
26+
toRecE,
27+
toRecP,
28+
toSumE,
29+
toSumP,
30+
)
1231

1332
{-
1433
TyDefs

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Pat.hs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
{-# LANGUAGE LambdaCase #-}
22

3-
module LambdaBuffers.Compiler.TypeClass.Pat where
3+
module LambdaBuffers.Compiler.TypeClass.Pat (
4+
Pat (..),
5+
Exp (..),
6+
Literal (..),
7+
ExpressionLike (..),
8+
toProdP,
9+
toSumP,
10+
toRecP,
11+
patList,
12+
toProdE,
13+
toSumE,
14+
toRecE,
15+
getLocalRefE,
16+
expList,
17+
matches,
18+
) where
419

520
import Data.Kind (Type)
621
import Data.Text (Text)
@@ -126,6 +141,22 @@ toRecE = RecE . foldr ConsE NilE
126141
toSumE :: [Exp] -> Exp
127142
toSumE = SumE . foldr ConsE NilE
128143

144+
-- | Extract the "type function" from an AppE Expr
145+
tyFunE :: Exp -> Maybe Exp
146+
tyFunE = \case
147+
AppE e1 _ -> case tyFunE e1 of
148+
Nothing -> Just e1
149+
Just e2 -> Just e2
150+
_ -> Nothing
151+
152+
getLocalRefE :: Exp -> Maybe Text
153+
getLocalRefE = \case
154+
RefE NilE (LitE (Name t)) -> Just t
155+
app@(AppE _ _) -> case tyFunE app of
156+
Just (RefE NilE (LitE (Name t))) -> Just t
157+
_ -> Nothing
158+
_ -> Nothing
159+
129160
{- Converts a pattern that consists of a well formed pattern list
130161
(i.e. patterns formed from :* and Nil) into a list of patterns.
131162
-}

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Pretty.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import Control.Lens.Operators ((^.))
1313
import Data.Generics.Labels ()
1414
import Data.Text qualified as T
1515
import LambdaBuffers.Compiler.ProtoCompat qualified as P
16-
import LambdaBuffers.Compiler.TypeClass.Pat
16+
import LambdaBuffers.Compiler.TypeClass.Pat (
17+
Exp (AppE, ConsE, DecE, LabelE, LitE, NilE, ProdE, RecE, RefE, SumE),
18+
Literal (ModuleName, Name, Opaque, TyVar),
19+
Pat (AppP, ConsP, DecP, LabelP, LitP, NilP, ProdP, RecP, RefP, SumP, VarP),
20+
expList,
21+
patList,
22+
)
1723
import LambdaBuffers.Compiler.TypeClass.Rules (
1824
Class (Class),
1925
Constraint (C),

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Rules.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
{-# LANGUAGE GADTs #-}
22
{-# LANGUAGE TypeFamilies #-}
33

4-
module LambdaBuffers.Compiler.TypeClass.Rules where
4+
module LambdaBuffers.Compiler.TypeClass.Rules (
5+
FQClassName (..),
6+
Class (..),
7+
Constraint (..),
8+
Rule (..),
9+
ruleHead,
10+
ruleClass,
11+
) where
512

613
import Data.Text (Text)
714

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Solve.hs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
{-# LANGUAGE LambdaCase #-}
22

3-
module LambdaBuffers.Compiler.TypeClass.Solve where
3+
module LambdaBuffers.Compiler.TypeClass.Solve (solve, inst, Overlap (..)) where
44

55
import Control.Monad.Except (throwError)
66
import Control.Monad.Reader (ReaderT, runReaderT)
77
import Control.Monad.Reader.Class (MonadReader (ask))
88
import Control.Monad.Writer.Class (MonadWriter (tell))
99
import Control.Monad.Writer.Strict (WriterT, execWriterT)
1010
import Data.Foldable (traverse_)
11-
import Data.List (foldl')
1211
import Data.Map qualified as M
1312
import Data.Set qualified as S
1413
import Data.Text (Text)
15-
import Debug.Trace (traceM)
16-
import LambdaBuffers.Compiler.TypeClass.Pat
17-
import LambdaBuffers.Compiler.TypeClass.Rules
14+
import LambdaBuffers.Compiler.TypeClass.Pat (Exp (AppE, ConsE, DecE, LabelE, LitE, NilE, ProdE, RecE, RefE, SumE), ExpressionLike ((*:), (*=)), Literal (TyVar), Pat (AppP, ConsP, DecP, LabelP, LitP, NilP, ProdP, RecP, RefP, SumP, VarP), matches)
15+
import LambdaBuffers.Compiler.TypeClass.Rules (Class (csupers), Constraint (C), Rule ((:<=)), ruleClass, ruleHead)
1816

1917
{- Pattern/Template/Unification variable substitution.
2018
Given a string that represents a variable name,
@@ -38,18 +36,7 @@ subV cxt = \case
3836
NilP -> NilE
3937

4038
inst :: Pat -> Exp
41-
inst = \case
42-
VarP v -> LitE (TyVar v)
43-
ConsP x xs -> ConsE (inst x) (inst xs)
44-
LabelP l x -> LabelE (inst l) (inst x)
45-
ProdP xs -> ProdE (inst xs)
46-
RecP xs -> RecE (inst xs)
47-
SumP xs -> SumE (inst xs)
48-
AppP t1 t2 -> AppE (inst t1) (inst t2)
49-
RefP n x -> RefE (inst n) (inst x)
50-
DecP a b c -> DecE (inst a) (inst b) (inst c)
51-
LitP l -> LitE l
52-
NilP -> NilE
39+
inst = subV M.empty
5340

5441
{- Performs substitution on an entire instance (the first argument) given the
5542
concrete types from a Pat (the second argument).
@@ -111,23 +98,20 @@ type SolveM = ReaderT [Rule Pat] (WriterT (S.Set (Constraint Exp)) (Either Overl
11198
`class Bar y => Foo y` without an `instance Bar X`)
11299
-}
113100
solveM :: Constraint Exp -> SolveM ()
101+
-- TODO(@gnumonik): Explain why we have this TyVar rule here
102+
solveM (C _ (LitE (TyVar _))) = pure ()
114103
solveM cst@(C c pat) =
115104
ask >>= \inScope ->
116-
-- First, we look for the most specific instance...
117105
case selectMatchingInstance pat c inScope of
118106
Left e -> case e of
119107
NoMatch -> tell $ S.singleton cst
120108
OverlappingMatches olps -> throwError $ Overlap cst olps
121-
-- If there is, we substitute the argument of the constraint to be solved into the matching rules
122109
Right rule -> case subst rule pat of
123-
-- If there are no additional constraints on the rule, we try to solve the superclasses
124110
C _ p :<= [] -> solveClassesFor p (csupers c)
125-
-- If there are additional constraints on the rule, we try to solve them
126111
C _ _ :<= is -> do
127112
traverse_ solveM is
128113
solveClassesFor pat (csupers c)
129114
where
130-
-- NOTE(@bladyjoker): The version w/ flip is more performant...
131115
-- Given a Pat and a list of Classes, attempt to solve the constraints
132116
-- constructed from the Pat and each Class
133117
solveClassesFor :: Exp -> [Class] -> SolveM ()

lambda-buffers-compiler/src/LambdaBuffers/Compiler/TypeClass/Utils.hs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,17 @@ import Data.Map qualified as M
2727
import Data.Set qualified as S
2828
import Data.Text qualified as T
2929

30-
import LambdaBuffers.Compiler.TypeClass.Compat
31-
import LambdaBuffers.Compiler.TypeClass.Rules
30+
import LambdaBuffers.Compiler.TypeClass.Compat (
31+
defToExp,
32+
modulename,
33+
tyToPat,
34+
)
35+
import LambdaBuffers.Compiler.TypeClass.Rules (
36+
Class (Class),
37+
Constraint (C),
38+
FQClassName (FQClassName),
39+
Rule ((:<=)),
40+
)
3241

3342
import Data.Text (Text)
3443
import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P (
@@ -41,7 +50,8 @@ import LambdaBuffers.Compiler.ProtoCompat.Types qualified as P (
4150
SourceInfo,
4251
TyClassRef (ForeignCI, LocalCI),
4352
)
44-
import LambdaBuffers.Compiler.TypeClass.Pat
53+
import LambdaBuffers.Compiler.TypeClass.Pat (Exp, Literal (ModuleName), Pat (AppP, ConsP, DecP, LabelP, LitP, NilP, ProdP, RecP, RefP, SumP, VarP))
54+
4555
import LambdaBuffers.Compiler.TypeClass.Pretty (pointies, (<///>))
4656
import LambdaBuffers.Compiler.TypeClass.Solve (Overlap (Overlap))
4757
import Prettyprinter (
@@ -70,8 +80,9 @@ data TypeClassError
7080
| LocalTyRefNotFound T.Text P.ModuleName
7181
| SuperclassCycleDetected [[FQClassName]]
7282
| CouldntSolveConstraints P.ModuleName [Constraint Exp] Instance
83+
| MalformedTyDef P.ModuleName Exp
7384
| BadInstance BasicConditionViolation
74-
deriving stock (Show)
85+
deriving stock (Show, Eq, Generic)
7586

7687
instance Pretty TypeClassError where
7788
pretty = \case
@@ -116,13 +127,19 @@ instance Pretty TypeClassError where
116127
<> line
117128
<> line
118129
<> indent 2 (vcat $ map pretty cs)
130+
MalformedTyDef mn xp ->
131+
"Error: Encountered malformed type definition:"
132+
<> line
133+
<> indent 2 (pretty xp)
134+
<> line
135+
<> indent 2 ("in module" <+> pretty mn)
119136
BadInstance bcv -> pretty bcv
120137

121138
data BasicConditionViolation
122139
= TyConInContext Instance (Constraint Pat)
123140
| OnlyTyVarsInHead Instance (Constraint Pat)
124141
| OverlapDetected Overlap
125-
deriving stock (Show)
142+
deriving stock (Show, Eq, Generic)
126143

127144
instance Pretty BasicConditionViolation where
128145
pretty = \case
@@ -156,23 +173,23 @@ all the type variables are distinct.
156173
3. The instance declarations must not overlap.
157174
-}
158175

159-
checkInstance :: Rule Pat -> Either BasicConditionViolation ()
176+
checkInstance :: Rule Pat -> Either TypeClassError ()
160177
checkInstance rule@(C cls hd :<= constraints) =
161178
case traverse cond1 constraints of
162179
Left e -> Left e
163180
Right _ -> case hd of
164-
VarP _ -> Left $ OnlyTyVarsInHead rule (C cls hd)
181+
VarP _ -> Left . BadInstance $ OnlyTyVarsInHead rule (C cls hd)
165182
-- NOTE: THIS ASSUMES THAT EVERYTHING HAS BEEN KIND-CHECKED AND
166183
-- THAT NO TYVARS HAVE KIND (* -> *). If every tyvar
167184
-- is of kind * and we have no MPTCs then the
168185
-- only case we have to worry about for condition 2 is
169186
-- a bare type variable in the head
170187
_ -> Right ()
171188
where
172-
cond1 :: Constraint Pat -> Either BasicConditionViolation ()
189+
cond1 :: Constraint Pat -> Either TypeClassError ()
173190
cond1 cst@(C _ p) = case p of
174191
VarP _ -> Right ()
175-
_ -> Left $ TyConInContext rule cst
192+
_ -> Left . BadInstance $ TyConInContext rule cst
176193

177194
{- This contains:
178195
- Everything needed to validate instances (in the form needed to do so)
@@ -269,11 +286,10 @@ getInstances ctable mn = foldM go S.empty
269286
let p = tyToPat h
270287
cs <- traverse goConstraint csts
271288
let inst = C cls p :<= cs
272-
check inst
289+
checkInstance inst
273290
pure $ S.insert inst acc
274291
where
275292
cref = tyRefToFQClassName (modulename mn) cn
276-
check = either (Left . BadInstance) pure . checkInstance
277293

278294
goConstraint :: P.Constraint -> Either TypeClassError (Constraint Pat)
279295
goConstraint (P.Constraint cn arg si') = case ctable ^? ix cref of

0 commit comments

Comments
 (0)