Skip to content
This repository was archived by the owner on Oct 18, 2021. It is now read-only.

Commit fcba0b7

Browse files
authoredApr 2, 2021
Mitigate exponential behaviour of inhabitance checker (#296)
This adds a simpler version of the inhabitance checker in pmcheck. Unlike pmcheck's, this does not use any contextual information, which means it can be memoized. There are some cases where the exponential behaviour is still present. For instance, consider the chain of sum types: type t1 'a = T1 of 'a type t2 'a 'b = T1L of (t1 'a) | T1R of (t1 'a) type t3 'a 'b = T3L of (t2 'a) | T3R of (t2 'a) (* ... *) We cannot statically tell if tn is inhabited for any type variable. I guess we could compile this to a predicate based on the type variables, but that may be going a bit far :). Fixes #294
1 parent 260c55c commit fcba0b7

12 files changed

+184
-24
lines changed
 

‎.ghci

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import qualified System.Environment as E
22
:seti -XOverloadedStrings -XFlexibleContexts
33

4-
:def r! const (pure (unlines [ ":!tools/repl.sh reload", "::r", ":!tput bel" ]))
5-
:def reload! const (pure (unlines [ ":!tools/repl.sh reload", "::r", ":!tput bel" ]))
4+
:def! r! const (pure (unlines [ ":!tools/repl.sh reload", "::r", ":!tput bel" ]))
5+
:def! reload! const (pure (unlines [ ":!tools/repl.sh reload", "::r", ":!tput bel" ]))
66

77
:def test \x -> pure $ ":!stack test --fast " ++ concat [ " --test-arguments '-p " ++ x ++ "'" | x /= "" ]
88
:def test! const (pure ":!stack test --fast --test-arguments '-r'")

‎amuletml.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ library
321321
, Types.Holes
322322
, Types.Kinds
323323
, Types.Unify
324+
, Types.Inhabited
324325
, Types.Infer.Let
325326
, Types.Infer.App
326327
, Types.Unify.Base

‎bin/Prove.hs

+5-5
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,11 @@ env =
204204
& constructors %~ mappend cons
205205
where
206206
cons = Set.fromList [TgInternal "L", TgInternal "R", TgInternal "Not", TgInternal "T", TgInternal "Equiv"]
207-
tys = Map.fromList [ (TgInternal "+", Set.fromList [TgInternal "L", TgInternal "R"])
208-
, (TgInternal "not", Set.fromList [TgInternal "Not"])
209-
, (TgInternal "ff", mempty)
210-
, (TgInternal "tt", Set.fromList [TgInternal "T"])
211-
, (TgInternal "<->", Set.fromList [TgInternal "Equiv"])
207+
tys = Map.fromList [ (TgInternal "+", TypeDef (Set.fromList [TgInternal "L", TgInternal "R"]) Unknown)
208+
, (TgInternal "not", TypeDef (Set.fromList [TgInternal "Not"]) Unknown)
209+
, (TgInternal "ff", TypeDef mempty Uninhabited)
210+
, (TgInternal "tt", TypeDef (Set.fromList [TgInternal "T"]) Inhabited)
211+
, (TgInternal "<->", TypeDef (Set.fromList [TgInternal "Equiv"]) Unknown)
212212
]
213213

214214
bindings = [ (TgInternal "+", TyType :-> TyType :-> TyType)

‎src/Syntax/Builtin.hs

+7-3
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ data BuiltinPowule = BM
197197
{ vars :: [(Var Resolved, Type Typed)]
198198
, types :: [(Var Resolved, Type Typed)]
199199
, modules :: [(Var Resolved, BuiltinPowule)]
200-
, constructors :: Map.Map (Var Resolved) (Set.Set (Var Typed))
200+
, constructors :: Map.Map (Var Resolved) T.TypeDef
201201
, classes :: [(Var Resolved, T.ClassInfo)]
202202
, families :: [(Var Resolved, T.TySymInfo)]
203203
}
@@ -249,7 +249,8 @@ builtins =
249249
]
250250

251251
, constructors = Map.fromList
252-
[ (tyListName, Set.fromList [cONSName, nILName] )
252+
[ ( tyListName
253+
, T.TypeDef (Set.fromList [cONSName, nILName]) T.Inhabited )
253254
]
254255

255256
, classes = [ (tyEqName, T.MagicInfo [] Nothing)
@@ -299,7 +300,10 @@ builtins =
299300
, ( [1, 3], [2, 0], internal ) ]
300301
Nothing )
301302
]
302-
, constructors = Map.fromList [(tyErrMsg_n, Set.fromList [tyeString_n, tyHCat_n, tyVCat_n, tyShowType_n])]
303+
, constructors = Map.fromList
304+
[ ( tyErrMsg_n
305+
, T.TypeDef (Set.fromList [tyeString_n, tyHCat_n, tyVCat_n, tyShowType_n]) T.Inhabited )
306+
]
303307
}
304308
) ]
305309
, families = [ (tyTypeError_n, T.TyFamInfo { T._tsName = tyTypeError_n

‎src/Syntax/Types.hs

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module Syntax.Types
55
( Telescope, one, foldTele, foldTeleM, teleFromList, mapTele, traverseTele, teleToList
66
, Scope(..), namesInScope, inScope, scopeToList, mapScope
7+
, Inhabited(..), TypeDef(..), tdConstructors, tdInhabited
78
, Env, freeInEnv, difference, envOf, scopeFromList, toMap
89
, names, typeVars, constructors, types, letBound, classes
910
, classDecs, tySyms, declaredHere
@@ -77,14 +78,26 @@ instance OrdPhrase p => Ixed (Scope p f) where
7778
instance OrdPhrase p => At (Scope p f) where
7879
at k f (Scope m) = Scope <$> at k f m
7980

81+
data Inhabited
82+
= Inhabited --- Type is unconditionally inhabited.
83+
| Uninhabited --- Type is unconditionally uninhabited.
84+
| Unknown --- Cannot be known.
85+
deriving (Eq, Show, Ord)
86+
87+
data TypeDef = TypeDef
88+
{ _tdConstructors :: Set.Set (Var Typed)
89+
, _tdInhabited :: Inhabited
90+
}
91+
deriving (Eq, Show, Ord)
92+
8093
data Env
8194
= Env { _names :: Scope Resolved (Type Typed)
8295
, _classes :: ImplicitScope ClassInfo Typed
8396
, _typeVars :: Set.Set (Var Typed)
8497
, _constructors :: Set.Set (Var Typed)
8598
, _letBound :: Set.Set (Var Typed)
8699
, _declaredHere :: Set.Set (Var Typed) -- Only types
87-
, _types :: Map.Map (Var Typed) (Set.Set (Var Typed))
100+
, _types :: Map.Map (Var Typed) TypeDef
88101
, _classDecs :: Map.Map (Var Typed) ClassInfo
89102
, _tySyms :: TySyms
90103
}
@@ -166,6 +179,7 @@ instance Show DerivingStrat where
166179
instance Ord DerivingStrat where
167180
_ `compare` _ = GT
168181

182+
makeLenses ''TypeDef
169183
makeLenses ''Env
170184
makeLenses ''ClassInfo
171185
makeLenses ''TySymInfo

‎src/Syntax/Verify.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ verifyProgram = traverse_ verifyStmt where
109109
TgName n _ -> n <> T.singleton '.'
110110
TgInternal v -> v <> T.singleton '.'
111111
ext s = s { env = env s & names %~ mapScope id (unqualifyWrt prefix)
112-
& types %~ fmap (Set.mapMonotonic (unqualifyVarWrt prefix)) }
112+
& types %~ fmap (tdConstructors %~ Set.mapMonotonic (unqualifyVarWrt prefix)) }
113113

114114
local ext $ verifyModule m
115115

‎src/Syntax/Verify/Pattern.hs

+10-5
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ constructors env kty vty = do
420420
where
421421
-- | Get the constructors for a given type
422422
ctors :: Type Typed -> Set.Set (Var Typed)
423-
ctors (TyCon v _) = fromMaybe (error $ "Cannot find constructors for " ++ show v) (env ^. (types . at v))
423+
ctors (TyCon v _) = maybe (error $ "Cannot find constructors for " ++ show v) _tdConstructors (env ^. (types . at v))
424424
ctors (TyApp f _) = ctors f
425425
ctors t = error $ "Cannot get type name from " ++ show (pretty t)
426426

@@ -496,10 +496,15 @@ inhabited env (AbsState st cs i)
496496
ctorCheck :: (MonadPlus m, MonadNamey m, MonadState CoverState m)
497497
=> Set.Set (Var Typed) -> Type Typed -> m ()
498498
ctorCheck c t
499-
| Just v <- concreteTy c t = do
500-
let c' = Set.insert v c
501-
(_, arg) <- constructors env t t
502-
maybe inhb (go c') arg
499+
| Just v <- concreteTy c t =
500+
case maybe Unknown _tdInhabited (env ^. (types . at v)) of
501+
Uninhabited -> mzero
502+
Inhabited -> inhb
503+
Unknown ->
504+
do
505+
let c' = Set.insert v c
506+
(_, arg) <- constructors env t t
507+
maybe inhb (go c') arg
503508
| otherwise = inhb
504509

505510
-- | Make a unification constraint

‎src/Types/Holes.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fill t | t == tyUnit = fake [t] $ pure . Literal LiUnit . head
121121
-- Sum types: we need to try each constructor.
122122
fill ty@(TyApps (TyCon ty_v ()) _) = once (knownImplication ty) <|> do
123123
-- Search all constructors for the type..
124-
con <- explore =<< view (psEnv . types . at ty_v . non mempty . to Set.toList)
124+
con <- explore =<< views (psEnv . types . at ty_v) (maybe mempty (Set.toList . _tdConstructors))
125125
#ifdef TRACE_TC
126126
traceM (displayS (keyword "try" <+> pretty con))
127127
#endif
@@ -178,7 +178,7 @@ fill _ = fail ""
178178
makeFunction :: MonadPs m => Type Typed -> Type Typed -> m (Expr Typed)
179179
makeFunction domain@(TyApps (TyCon t ()) _) body_t = fake [domain] $ \[ann] -> do
180180
span <- view psAnn
181-
cons <- view (psEnv . types . at t . non undefined . to Set.toList)
181+
cons <- view (psEnv . types . at t . non undefined . tdConstructors . to Set.toList)
182182

183183
arms <- for cons $ \con -> do
184184
(ty, _) <- skolGadt con =<<
@@ -205,7 +205,7 @@ isSum (TyOperator l o r) = isSum (TyApps o [l, r])
205205
isSum (TyApps (TyCon t ()) _) = do
206206
x <- view (psEnv . types . at t)
207207
pure $ case x of
208-
Just t -> Set.size t /= 1
208+
Just t -> Set.size (t ^. tdConstructors) /= 1
209209
Nothing -> False
210210
isSum _ = pure False
211211

@@ -289,7 +289,7 @@ assume ty@(TyExactRows xs) k = go [] xs k where
289289
go pats [] k = fake [ty] $ \[record] -> k (PRecord (reverse pats) record)
290290

291291
assume ty@(TyApps (TyCon c ()) xs) k = fake [ty] $ \[pat] -> do
292-
cs <- view (psEnv . types . at c . non mempty . to Set.toList)
292+
cs <- views (psEnv . types . at c) (maybe mempty (Set.toList . _tdConstructors))
293293
case cs of
294294
[con] -> do
295295
~(ty, _) <- skolGadt con =<<

‎src/Types/Infer.hs

+6-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Types.Infer.Builtin
4242
import Types.Infer.Class
4343
import Types.Infer.App
4444
import Types.Infer.Let
45+
import Types.Inhabited
4546
import Types.Kinds
4647
import Types.Unify
4748

@@ -536,8 +537,9 @@ inferProg (decl@(TypeDecl am n tvs cs ann):prg) = do
536537
(for cs (\con -> retcons (addBlame (BecauseOf con)) (inferCon vars retTy con)))
537538

538539
let ts' = Set.fromList (map fst ts)
540+
inhabited <- asks (\env -> inhabited env n cs')
539541
local ( (names %~ focus (teleFromList ts))
540-
. (types %~ Map.insert n ts')
542+
. (types %~ Map.insert n (TypeDef ts' inhabited))
541543
. (constructors %~ Set.union ts') ) $
542544
cont (Just cs')
543545

@@ -640,7 +642,8 @@ inferMod (ModStruct bod a) = do
640642
let diff = env `difference` extEnv
641643
env' = (declaredHere .~ mempty)
642644
. (names .~ mapScope (append prefix) (qualifyWrt prefix) (diff ^. names))
643-
. (types .~ (Set.mapMonotonic (append prefix) <$> Map.mapKeysMonotonic (append prefix) (diff ^. types)))
645+
. (types .~ ((tdConstructors %~ Set.mapMonotonic (append prefix))
646+
<$> Map.mapKeysMonotonic (append prefix) (diff ^. types)))
644647
$ env
645648
in extEnv <> env')
646649

@@ -774,7 +777,7 @@ unqualify (ModRef v _) =
774777
TgName v _ -> v <> T.singleton '.'
775778
TgInternal v -> v <> T.singleton '.'
776779
in (names %~ mapScope id (unqualifyWrt prefix))
777-
. (types %~ fmap (Set.mapMonotonic (unqualifyVarWrt prefix)))
780+
. (types %~ fmap (tdConstructors %~ Set.mapMonotonic (unqualifyVarWrt prefix)))
778781

779782
unqualify _ = id
780783

‎src/Types/Inhabited.hs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{-# LANGUAGE FlexibleContexts, TypeFamilies, ScopedTypeVariables #-}
2+
3+
{-| A simplified version of pmcheck's inhabited type checker. This determines
4+
if a type is:
5+
- Trivially inhabitable (i.e. has an inhabitable constructor),
6+
- Uninhabitable (all constructors are uninhabitable), or
7+
- Unknown (relies on additional information, such as constraints or type variables)
8+
-}
9+
module Types.Inhabited (inhabited) where
10+
11+
import Control.Lens
12+
13+
import Syntax.Var
14+
import Syntax.Type
15+
import Syntax.Types
16+
import Syntax.Toplevel
17+
18+
inhabited :: forall p. Var p ~ VarResolved
19+
=> Env -> Var p -> [Constructor p] -> Inhabited
20+
inhabited env tyName = goCtors where
21+
goCtors :: [Constructor p] -> Inhabited
22+
goCtors [] = Uninhabited
23+
goCtors (UnitCon{}:_) = Inhabited
24+
goCtors (ArgCon _ _ t _:cs) = orInhabited (goType t) (goCtors cs)
25+
goCtors (GadtCon{}:cs) =
26+
-- :( I cannot think of a way to do this sensibly, so we let the verifier
27+
-- check this for us.
28+
orInhabited Unknown (goCtors cs)
29+
30+
goTypes :: [Type p] -> Inhabited
31+
goTypes [] = Inhabited
32+
goTypes (t:ts) =
33+
case goType t of
34+
Inhabited -> goTypes ts
35+
x -> x
36+
37+
goType :: Type p -> Inhabited
38+
-- We can't know if a type variable is inhabited or not!
39+
goType TyVar{} = Unknown
40+
41+
goType (TyApp t _) = goType t
42+
goType (TyCon v _)
43+
| v == tyName = Unknown
44+
| otherwise = case env ^. types . at v of
45+
Nothing -> Inhabited -- Abstract types are considered inhabited.
46+
Just t -> t ^. tdInhabited
47+
48+
-- We don't really have the concept of uninhabited types, so we assume
49+
-- type names are.
50+
goType TyPromotedCon{} = Inhabited
51+
-- For now, we'll just assume all Pi types are inhabited. Thankfully,
52+
-- they all are.
53+
goType TyPi{} = Inhabited
54+
-- All the boring types: just determine if the children are inhabited
55+
goType (TyRows f fs) = goTypes (f:map snd fs)
56+
goType (TyExactRows fs) = goTypes (snd <$> fs)
57+
goType (TyTuple l r) = goTypes [l, r]
58+
goType (TyOperator l v r) = goType (TyApp (TyApp v l) r)
59+
goType (TyWildcard t) = maybe Unknown goType t
60+
goType (TyParens t) = goType t
61+
62+
goType TySkol{} = Inhabited
63+
goType (TyWithConstraints _ t) = goType t
64+
goType TyType = Inhabited
65+
goType TyLit{} = Inhabited
66+
goType TyTupleL{} = Inhabited
67+
68+
orInhabited :: Inhabited -> Inhabited -> Inhabited
69+
orInhabited Inhabited _ = Inhabited
70+
orInhabited Uninhabited x = x
71+
orInhabited Unknown Inhabited = Inhabited
72+
orInhabited Unknown _ = Unknown
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
(**
2+
Checks exponential behaviour of the inhabitation checker.
3+
4+
Note: putting a type variable in all of these will still cause issues. We just
5+
rely on the fact that you'll have concrete boundaries somewhere. I'm not smart
6+
enough to do this correctly.
7+
*)
8+
9+
type t0 = T0 of int
10+
type t1 = T1 of t0 * t0
11+
type t2 = T2 of t1 * t1
12+
type t3 = T3 of t2 * t2
13+
type t4 = T4 of t3 * t3
14+
type t5 = T5 of t4 * t4
15+
type t6 = T6 of t5 * t5
16+
type t7 = T7 of t6 * t6
17+
type t8 = T8 of t7 * t7
18+
type t9 = T9 of t8 * t8
19+
type t10 = T10 of t9 * t9
20+
type t11 = T11 of t10 * t10
21+
type t12 = T12 of t11 * t11
22+
type t13 = T13 of t12 * t12
23+
type t14 = T14 of t13 * t13
24+
type t15 = T15 of t14 * t14
25+
type t16 = T16 of t15 * t15
26+
type t17 = T17 of t16 * t16
27+
type t18 = T18 of t17 * t17
28+
type t19 = T19 of t18 * t18
29+
type t20 = T20 of t19 * t19
30+
type t21 = T21 of t20 * t20
31+
type t22 = T22 of t21 * t21
32+
type t23 = T23 of t22 * t22
33+
type t24 = T24 of t23 * t23
34+
type t25 = T25 of t24 * t24
35+
type t26 = T26 of t25 * t25
36+
type t27 = T27 of t26 * t26
37+
type t28 = T28 of t27 * t27
38+
type t29 = T29 of t28 * t28
39+
type t30 = T30 of t29 * t29
40+
type t31 = T31 of t30 * t30
41+
type t32 = T32 of t31 * t31
42+
type t33 = T33 of t32 * t32
43+
type t34 = T34 of t33 * t33
44+
type t35 = T35 of t34 * t34
45+
type t36 = T36 of t35 * t35
46+
type t37 = T37 of t36 * t36
47+
type t38 = T38 of t37 * t37
48+
type t39 = T39 of t38 * t38
49+
type t40 = T40 of t39 * t39
50+
type t41 = T41 of t40 * t40
51+
type t42 = T42 of t41 * t41
52+
type t43 = T43 of t42 * t42
53+
type t44 = T44 of t43 * t43
54+
type t45 = T45 of t44 * t44
55+
type t46 = T46 of t45 * t45
56+
type t47 = T47 of t46 * t46
57+
type t48 = T48 of t47 * t47
58+
type t49 = T49 of t48 * t48
59+
type t50 = T50 of t49 * t49
60+
61+
let f (T50 _) = ()

‎tests/verify/pmcheck/exponential_inhabit.out

Whitespace-only changes.

0 commit comments

Comments
 (0)
This repository has been archived.