Skip to content

Commit 97ee3d4

Browse files
committed
Check LHS arguments are polymorphic enough
We already did this, but missed a few cases due to the way arguments are elaborated. So now, when checking an LHS, we don't allow LHS argument types to be inferred from the pattern, but rather they must be inferred from elsewhere. To do this, we keep track of the constraints which would be solved when inferring the type, and make sure they don't solve any new metavariables. Fixes #1510, and also now gets the error location right as a bonus!
1 parent 19cb268 commit 97ee3d4

File tree

27 files changed

+220
-73
lines changed

27 files changed

+220
-73
lines changed

src/Core/Coverage.idr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ isEmpty : {vars : _} ->
141141
isEmpty defs env (NTCon fc n t a args)
142142
= do Just nty <- lookupDefExact n (gamma defs)
143143
| _ => pure False
144-
log "coverage.empty" 10 $ "Checking type: " ++ show nty
145144
case nty of
146145
TCon _ _ _ _ flags _ cons _
147146
=> if not (external flags)

src/Core/Unify.idr

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,10 +831,11 @@ mutual
831831
Core UnifyResult
832832
solveHole loc mode env mname mref margs margs' locs submv solfull stm solnf
833833
= do defs <- get Ctxt
834+
ust <- get UST
834835
empty <- clearDefs defs
835836
-- if the terms are the same, this isn't a solution
836837
-- but they are already unifying, so just return
837-
if solutionHeadSame solnf
838+
if solutionHeadSame solnf || inNoSolve mref (noSolve ust)
838839
then pure success
839840
else -- Rather than doing the occurs check here immediately,
840841
-- we'll wait until all metavariables are resolved, and in
@@ -846,6 +847,12 @@ mutual
846847
instantiate loc mode env mname mref (length margs) hdef locs solfull stm
847848
pure $ solvedHole mref
848849
where
850+
inNoSolve : Int -> IntMap () -> Bool
851+
inNoSolve i ns
852+
= case lookup i ns of
853+
Nothing => False
854+
Just _ => True
855+
849856
-- Only need to check the head metavar is the same, we've already
850857
-- checked the rest if they are the same (and we couldn't instantiate it
851858
-- anyway...)

src/Core/UnifyState.idr

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ data Constraint : Type where
4343
-- A resolved constraint
4444
Resolved : Constraint
4545

46+
-- a constraint on the LHS arising from checking an argument in a position
47+
-- where we expect a polymorphic type. If, in the end, the expected type is
48+
-- polymorphic but the argument is concrete, then the pattern match is too
49+
-- specific
50+
public export
51+
data PolyConstraint : Type where
52+
MkPolyConstraint : {vars : _} ->
53+
FC -> Env Term vars ->
54+
(arg : Term vars) ->
55+
(expty : NF vars) ->
56+
(argty : NF vars) -> PolyConstraint
57+
4658
public export
4759
record UState where
4860
constructor MkUState
@@ -57,6 +69,12 @@ record UState where
5769
-- user defined hole names, which don't need
5870
-- to have been solved
5971
constraints : IntMap Constraint -- map for finding constraints by ID
72+
noSolve : IntMap () -- Names not to solve
73+
-- If we're checking an LHS, then checking an argument can't
74+
-- solve its own type, or we might end up with something
75+
-- less polymorphic than it should be
76+
polyConstraints : List PolyConstraint -- constraints which need to be solved
77+
-- successfully to check an LHS is polymorphic enough
6078
dotConstraints : List (Name, DotReason, Constraint) -- dot pattern constraints
6179
nextName : Int
6280
nextConstraint : Int
@@ -77,6 +95,8 @@ initUState = MkUState
7795
, currentHoles = empty
7896
, delayedHoles = empty
7997
, constraints = empty
98+
, noSolve = empty
99+
, polyConstraints = []
80100
, dotConstraints = []
81101
, nextName = 0
82102
, nextConstraint = 0
@@ -180,6 +200,20 @@ removeHoleName n
180200
| Nothing => pure ()
181201
removeHole i
182202

203+
export
204+
addNoSolve : {auto u : Ref UST UState} ->
205+
Int -> Core ()
206+
addNoSolve i
207+
= do ust <- get UST
208+
put UST (record { noSolve $= insert i () } ust)
209+
210+
export
211+
removeNoSolve : {auto u : Ref UST UState} ->
212+
Int -> Core ()
213+
removeNoSolve i
214+
= do ust <- get UST
215+
put UST (record { noSolve $= delete i } ust)
216+
183217
export
184218
saveHoles : {auto u : Ref UST UState} ->
185219
Core (IntMap (FC, Name))
@@ -281,6 +315,17 @@ addDot fc env dotarg x reason y
281315
((dotarg, reason, MkConstraint fc False env xnf ynf) ::)
282316
} ust)
283317

318+
export
319+
addPolyConstraint : {vars : _} ->
320+
{auto u : Ref UST UState} ->
321+
FC -> Env Term vars -> Term vars -> NF vars -> NF vars ->
322+
Core ()
323+
addPolyConstraint fc env arg x@(NApp _ (NMeta _ _ _) _) y
324+
= do ust <- get UST
325+
put UST (record { polyConstraints $= ((MkPolyConstraint fc env arg x y) ::) } ust)
326+
addPolyConstraint fc env arg x y
327+
= pure ()
328+
284329
mkConstantAppArgs : {vars : _} ->
285330
Bool -> FC -> Env Term vars ->
286331
(wkns : List Name) ->

src/Idris/Error.idr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ perror (BadDotPattern fc env reason x y)
428428
<++> parens (pretty reason) <+> dot) <+> line <+> !(ploc fc)
429429
perror (MatchTooSpecific fc env tm)
430430
= pure $ errorDesc (reflow "Can't match on" <++> code !(pshow env tm)
431-
<++> reflow "as it has a polymorphic type.") <+> line <+> !(ploc fc)
431+
<++> reflow "as it must have a polymorphic type.") <+> line <+> !(ploc fc)
432432
perror (BadImplicit fc str)
433433
= pure $ errorDesc (reflow "Can't infer type for unbound implicit name" <++> code (pretty str) <+> dot)
434434
<+> line <+> !(ploc fc) <+> line <+> reflow "Suggestion: try making it a bound implicit."

src/TTImp/Elab.idr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Core.Metadata
99
import Core.Normalise
1010
import Core.UnifyState
1111
import Core.Unify
12+
import Core.Value
1213

1314
import TTImp.Elab.Check
1415
import TTImp.Elab.Delayed

src/TTImp/Elab/Ambiguity.idr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ checkAlternative rig elabinfo nest env fc (UniqueDefault def) alts mexpected
383383
checkImp rig (addAmbig alts' (getName t) elabinfo)
384384
nest env t
385385
(Just exp'))) alts'))
386-
(do log "elab" 5 "All failed, running default"
386+
(do log "elab.ambiguous" 5 "All failed, running default"
387387
checkImp rig (addAmbig alts' (getName def) elabinfo)
388388
nest env def (Just exp'))
389389
else exactlyOne' True fc env
@@ -441,5 +441,5 @@ checkAlternative rig elabinfo nest env fc uniq alts mexpected
441441
-- way that allows one pass)
442442
solveConstraints solvemode Normal
443443
solveConstraints solvemode Normal
444-
log "elab" 10 $ show (getName t) ++ " success"
444+
log "elab.ambiguous" 10 $ show (getName t) ++ " success"
445445
pure res)) alts')

src/TTImp/Elab/App.idr

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,6 @@ isHole : NF vars -> Bool
153153
isHole (NApp _ (NMeta _ _ _) _) = True
154154
isHole _ = False
155155

156-
-- Return whether we already know the return type of the given function
157-
-- type. If we know this, we can possibly infer some argument types before
158-
-- elaborating them, which might help us disambiguate things more easily.
159-
concrete : Defs -> Env Term vars -> NF vars -> Core Bool
160-
concrete defs env (NBind fc _ (Pi _ _ _ _) sc)
161-
= do sc' <- sc defs (toClosure defaultOpts env (Erased fc False))
162-
concrete defs env sc'
163-
concrete defs env (NDCon _ _ _ _ _) = pure True
164-
concrete defs env (NTCon _ _ _ _ _) = pure True
165-
concrete defs env (NPrimVal _ _) = pure True
166-
concrete defs env _ = pure False
167-
168156
mutual
169157
makeImplicit : {vars : _} ->
170158
{auto c : Ref Ctxt Defs} ->
@@ -324,9 +312,11 @@ mutual
324312
needsDelayLHS (IAutoApp _ f _) = needsDelayLHS f
325313
needsDelayLHS (INamedApp _ f _ _) = needsDelayLHS f
326314
needsDelayLHS (IAlternative _ _ _) = pure True
315+
needsDelayLHS (IAs _ _ _ _ t) = needsDelayLHS t
327316
needsDelayLHS (ISearch _ _) = pure True
328317
needsDelayLHS (IPrimVal _ _) = pure True
329318
needsDelayLHS (IType _) = pure True
319+
needsDelayLHS (IWithUnambigNames _ _ t) = needsDelayLHS t
330320
needsDelayLHS _ = pure False
331321

332322
onLHS : ElabMode -> Bool
@@ -355,21 +345,6 @@ mutual
355345
Bind _ _ (Lam _ _ _ _) _ => registerDot rig env fc NotConstructor tm ty
356346
_ => pure (tm, ty)
357347

358-
359-
checkPatTyValid : {vars : _} ->
360-
{auto c : Ref Ctxt Defs} ->
361-
FC -> Defs -> Env Term vars ->
362-
NF vars -> Term vars -> Glued vars -> Core ()
363-
checkPatTyValid fc defs env (NApp _ (NMeta n i _) _) arg got
364-
= do Just gdef <- lookupCtxtExact (Resolved i) (gamma defs)
365-
| Nothing => pure ()
366-
when (isErased (multiplicity gdef)) $ do
367-
-- Argument is only valid if gotnf is not a concrete type
368-
gotnf <- getNF got
369-
when !(concrete defs env gotnf) $
370-
throw (MatchTooSpecific fc env arg)
371-
checkPatTyValid fc defs env _ _ _ = pure ()
372-
373348
dotErased : {auto c : Ref Ctxt Defs} -> (argty : NF vars) ->
374349
Maybe Name -> Nat -> ElabMode -> RigCount -> RawImp -> Core RawImp
375350
dotErased argty mn argpos (InLHS lrig ) rig tm
@@ -462,20 +437,34 @@ mutual
462437
aty' <- nf defs env metaty
463438
logNF "elab" 10 ("Now trying " ++ show nm ++ " " ++ show arg) env aty'
464439

465-
res <- check argRig elabinfo nest env arg (Just $ glueBack defs env aty')
440+
-- On the LHS, checking an argument can't resolve its own type,
441+
-- it must be resolved from elsewhere. Otherwise we might match
442+
-- on things which are too specific for their polymorphic type.
443+
when (onLHS (elabMode elabinfo)) $
444+
case aty' of
445+
NApp _ (NMeta _ i _) _ =>
446+
do Just gdef <- lookupCtxtExact (Resolved i) (gamma defs)
447+
| Nothing => pure ()
448+
when (isErased (multiplicity gdef)) $ addNoSolve i
449+
_ => pure ()
450+
res <- check argRig (record { topLevel = False } elabinfo) nest env arg (Just $ glueBack defs env aty')
451+
when (onLHS (elabMode elabinfo)) $
452+
case aty' of
453+
NApp _ (NMeta _ i _) _ => removeNoSolve i
454+
_ => pure ()
455+
466456
(argv, argt) <-
467457
if not (onLHS (elabMode elabinfo))
468458
then pure res
469459
else do let (argv, argt) = res
470-
checkPatTyValid fc defs env aty' argv argt
471460
checkValidPattern rig env fc argv argt
472461

473462
defs <- get Ctxt
474463
-- If we're on the LHS, reinstantiate it with 'argv' because it
475464
-- *may* have as patterns in it and we need to retain them.
476465
-- (As patterns are a bit of a hack but I don't yet see a
477466
-- better way that leads to good code...)
478-
logTerm "elab" 5 ("Solving " ++ show metaval ++ " with") argv
467+
logTerm "elab" 10 ("Solving " ++ show metaval ++ " with") argv
479468
ok <- solveIfUndefined env metaval argv
480469
-- If there's a constraint, make a constant, but otherwise
481470
-- just return the term as expected
@@ -503,7 +492,7 @@ mutual
503492
(\t => pure (Just !(toFullNames!(getTerm t))))
504493
expty
505494
pure ("Overall expected type: " ++ show ety))
506-
res <- check argRig elabinfo
495+
res <- check argRig (record { topLevel = False } elabinfo)
507496
nest env arg (Just (glueBack defs env aty))
508497
(argv, argt) <-
509498
if not (onLHS (elabMode elabinfo))

src/TTImp/Elab/Check.idr

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ record EState (vars : List Name) where
135135
allPatVars : List Name
136136
-- Holes standing for pattern variables, which we'll delete
137137
-- once we're done elaborating
138+
polyMetavars : List (FC, Env Term vars, Term vars, Term vars)
139+
-- Metavars which need to be a polymorphic type at the end
140+
-- of elaboration. If they aren't, it means we're trying to
141+
-- pattern match on a type that we don't have available.
138142
delayDepth : Nat -- if it gets too deep, it gets slow, so fail quicker
139143
linearUsed : List (Var vars)
140144
saveHoles : NameMap () -- things we'll need to save to TTC, even if solved
@@ -159,6 +163,7 @@ initEStateSub n env sub = MkEState
159163
, bindIfUnsolved = []
160164
, lhsPatVars = []
161165
, allPatVars = []
166+
, polyMetavars = []
162167
, delayDepth = Z
163168
, linearUsed = []
164169
, saveHoles = empty
@@ -187,6 +192,7 @@ weakenedEState {e}
187192
, boundNames $= map wknTms
188193
, toBind $= map wknTms
189194
, linearUsed $= map weaken
195+
, polyMetavars = [] -- no binders on LHS
190196
} est
191197
pure eref
192198
where
@@ -212,6 +218,7 @@ strengthenedEState {n} {vars} c e fc env
212218
, boundNames = bns
213219
, toBind = todo
214220
, linearUsed $= mapMaybe dropTop
221+
, polyMetavars = [] -- no binders on LHS
215222
} est
216223

217224
where
@@ -287,6 +294,28 @@ inScope {c} {e} fc env elab
287294
put {ref=e} EST st'
288295
pure res
289296

297+
export
298+
mustBePoly : {auto e : Ref EST (EState vars)} ->
299+
FC -> Env Term vars ->
300+
Term vars -> Term vars -> Core ()
301+
mustBePoly fc env tm ty
302+
= do est <- get EST
303+
put EST (record { polyMetavars $= ((fc, env, tm, ty) :: ) } est)
304+
305+
-- Return whether we already know the return type of the given function
306+
-- type. If we know this, we can possibly infer some argument types before
307+
-- elaborating them, which might help us disambiguate things more easily.
308+
export
309+
concrete : Defs -> Env Term vars -> NF vars -> Core Bool
310+
concrete defs env (NBind fc _ (Pi _ _ _ _) sc)
311+
= do sc' <- sc defs (toClosure defaultOpts env (Erased fc False))
312+
concrete defs env sc'
313+
concrete defs env (NDCon _ _ _ _ _) = pure True
314+
concrete defs env (NTCon _ _ _ _ _) = pure True
315+
concrete defs env (NPrimVal _ _) = pure True
316+
concrete defs env (NType _) = pure True
317+
concrete defs env _ = pure False
318+
290319
export
291320
updateEnv : {new : _} ->
292321
Env Term new -> SubVars new vars ->

src/TTImp/Elab/Delayed.idr

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ delayOnFailure : {vars : _} ->
7070
Core (Term vars, Glued vars)
7171
delayOnFailure fc rig env expected pred pri elab
7272
= do est <- get EST
73+
ust <- get UST
74+
let nos = noSolve ust -- remember the holes we shouldn't solve
7375
handle (elab False)
7476
(\err =>
7577
do est <- get EST
@@ -85,7 +87,15 @@ delayOnFailure fc rig env expected pred pri elab
8587
defs <- get Ctxt
8688
put UST (record { delayedElab $=
8789
((pri, ci, localHints defs,
88-
mkClosedElab fc env (deeper (elab True))) :: ) }
90+
mkClosedElab fc env
91+
(deeper
92+
(do ust <- get UST
93+
let nos' = noSolve ust
94+
put UST (record { noSolve = nos } ust)
95+
res <- elab True
96+
ust <- get UST
97+
put UST (record { noSolve = nos' } ust)
98+
pure res))) :: ) }
8999
ust)
90100
pure (dtm, expected)
91101
else throw err)
@@ -103,6 +113,8 @@ delayElab : {vars : _} ->
103113
Core (Term vars, Glued vars)
104114
delayElab {vars} fc rig env exp pri elab
105115
= do est <- get EST
116+
ust <- get UST
117+
let nos = noSolve ust -- remember the holes we shouldn't solve
106118
nm <- genName "delayed"
107119
expected <- mkExpected exp
108120
(ci, dtm) <- newDelayed fc linear env nm !(getTerm expected)
@@ -111,7 +123,14 @@ delayElab {vars} fc rig env exp pri elab
111123
ust <- get UST
112124
defs <- get Ctxt
113125
put UST (record { delayedElab $=
114-
((pri, ci, localHints defs, mkClosedElab fc env elab) :: ) }
126+
((pri, ci, localHints defs, mkClosedElab fc env
127+
(do ust <- get UST
128+
let nos' = noSolve ust
129+
put UST (record { noSolve = nos } ust)
130+
res <- elab
131+
ust <- get UST
132+
put UST (record { noSolve = nos' } ust)
133+
pure res)) :: ) }
115134
ust)
116135
pure (dtm, expected)
117136
where

0 commit comments

Comments
 (0)