@@ -17,15 +17,22 @@ public section
1717
1818namespace Lean.Meta.Match
1919
20+ register_builtin_option backwards.match.sparseCases : Bool := {
21+ defValue := true
22+ descr := "if true (the default), generate and use sparse case constructs when splitting inductive
23+ types. In some cases this will prevent Lean from noticing that a match statement is complete
24+ because it performs less case-splitting for the unreachable case. In this case, give explicit
25+ patterns to perform the deeper split with `by contradiction` as the right-hand side.
26+ ,"
27+ }
28+
2029register_builtin_option backwards.match.rowMajor : Bool := {
2130 defValue := true
22- group := "bootstrap"
2331 descr := "If true (the default), match compilation will split the discrimnants based \
2432 on position of the first constructor pattern in the first alternative. If false, \
2533 it splits them from left to right, which can lead to unnecessary code bloat."
2634}
2735
28-
2936private def mkIncorrectNumberOfPatternsMsg [ToMessageData α]
3037 (discrepancyKind : String) (expected actual : Nat) (pats : List α) :=
3138 let patternsMsg := MessageData.joinSep (pats.map toMessageData) ", "
@@ -162,6 +169,12 @@ private def hasArrayLitPattern (p : Problem) : Bool :=
162169 | .arrayLit .. :: _ => true
163170 | _ => false
164171
172+ private def hasVarOrInaccessiblePattern (p : Problem) : Bool :=
173+ p.alts.any fun alt => match alt.patterns with
174+ | .inaccessible _ :: _ => true
175+ | .var _ :: _ => true
176+ | _ => false
177+
165178private def isVariableTransition (p : Problem) : Bool :=
166179 p.alts.all fun alt => match alt.patterns with
167180 | .inaccessible _ :: _ => true
@@ -341,6 +354,35 @@ where
341354 msg := msg ++ m!"\n {lhs} ≋ {rhs}"
342355 throwErrorAt alt.ref msg
343356
357+ private abbrev isCtorIdxIneq? (e : Expr) : Option FVarId := do
358+ if let some (_, lhs, _rhs) := e.ne? then
359+ if
360+ lhs.isApp &&
361+ lhs.getAppFn.isConst &&
362+ (`ctorIdx).isSuffixOf lhs.getAppFn.constName! && -- This should be an env extension maybe
363+ lhs.appArg!.isFVar
364+ then
365+ return lhs.appArg!.fvarId!
366+ none
367+
368+ private partial def contradiction (mvarId : MVarId) : MetaM Bool := do
369+ mvarId.withContext do
370+ withTraceNode `Meta.Match.match (msg := (return m!"{exceptBoolEmoji ·} Match.contradiction" )) do
371+ trace[Meta.Match.match] m!"Match.contradiction:\n {mvarId}"
372+ if (← mvarId.contradictionCore {}) then
373+ trace[Meta.Match.match] "Contradiction found!"
374+ return true
375+ else
376+ -- Try harder by splitting `ctorIdx x ≠ 23` assumptions
377+ for localDecl in (← getLCtx) do
378+ if let some fvarId := isCtorIdxIneq? localDecl.type then
379+ trace[Meta.Match.match] "splitting ctorIdx assumption {localDecl.type}"
380+ let subgoals ← mvarId.cases fvarId
381+ return ← subgoals.allM (contradiction ·.mvarId)
382+
383+ mvarId.admit
384+ return false
385+
344386/--
345387Try to solve the problem by using the first alternative whose pending constraints can be resolved.
346388-/
@@ -352,10 +394,10 @@ where
352394 go (alts : List Alt) : StateRefT State MetaM Unit := do
353395 match alts with
354396 | [] =>
397+ let mvarId ← p.mvarId.exfalso
355398 /- TODO: allow users to configure which tactic is used to close leaves. -/
356- unless (← p.mvarId.contradictionCore {}) do
357- trace[Meta.Match.match] "missing alternative"
358- p.mvarId.admit
399+ unless (← contradiction mvarId) do
400+ trace[Meta.Match.match] "contradiction failed, missing alternative"
359401 modify fun s => { s with counterExamples := p.examples :: s.counterExamples }
360402 | alt :: _ =>
361403 solveCnstrs p.mvarId alt
@@ -516,13 +558,29 @@ private def throwCasesException (p : Problem) (ex : Exception) : MetaM α := do
516558 "- <constructor> = <constructor>, examples: List.cons x xs = List.cons y ys, and List.cons x xs = List.nil"
517559 | _ => throw ex
518560
561+ private def collectCtors (p : Problem) : Array Name :=
562+ p.alts.foldl (init := #[]) fun ctors alt =>
563+ match alt.patterns with
564+ | .ctor n _ _ _ :: _ => if ctors.contains n then ctors else ctors.push n
565+ | _ => ctors
566+
519567private def processConstructor (p : Problem) : MetaM (Array Problem) := do
520568 trace[Meta.Match.match] "constructor step"
521569 let x :: xs := p.vars | unreachable!
570+ let interestingCtors? ←
571+ -- We use a sparse case analysis only if there is at least one non-constructor pattern,
572+ -- but not just because there are constructors missing (in that case we benefit from
573+ -- the eager split in ruling out constructors by type or by a more explicit error message)
574+ if backwards.match.sparseCases.get (← getOptions) && hasVarOrInaccessiblePattern p then
575+ let ctors := collectCtors p
576+ trace[Meta.Match.match] "using sparse cases: {ctors}"
577+ pure (some ctors)
578+ else
579+ pure none
522580 let subgoals? ← commitWhenSome? do
523581 let subgoals ←
524582 try
525- p.mvarId.cases x.fvarId!
583+ p.mvarId.cases x.fvarId! (interestingCtors? := interestingCtors?)
526584 catch ex =>
527585 if p.alts.isEmpty then
528586 /- If we have no alternatives and dependent pattern matching fails, then a "missing cases" error is better than a "stuck" error message. -/
@@ -545,28 +603,42 @@ private def processConstructor (p : Problem) : MetaM (Array Problem) := do
545603 return some subgoals
546604 let some subgoals := subgoals? | return #[{ p with vars := xs }]
547605 subgoals.mapM fun subgoal => subgoal.mvarId.withContext do
548- let subst := subgoal.subst
549- let fields := subgoal.fields.toList
550- let newVars := fields ++ xs
551- let newVars := newVars.map fun x => x.applyFVarSubst subst
552- let subex := Example.ctor subgoal.ctorName <| fields.map fun field => match field with
553- | .fvar fvarId => Example.var fvarId
554- | _ => Example.underscore -- This case can happen due to dependent elimination
555- let examples := p.examples.map <| Example.replaceFVarId x.fvarId! subex
556- let examples := examples.map <| Example.applyFVarSubst subst
557- let newAlts := p.alts.filter fun alt => match alt.patterns with
558- | .ctor n .. :: _ => n == subgoal.ctorName
559- | .var _ :: _ => true
560- | .inaccessible _ :: _ => true
561- | _ => false
562- let newAlts := newAlts.map fun alt => alt.applyFVarSubst subst
563- let newAlts ← newAlts.mapM fun alt => do
564- match alt.patterns with
565- | .ctor _ _ _ fields :: ps => return { alt with patterns := fields ++ ps }
566- | .var _ :: _ => expandVarIntoCtor alt subgoal.ctorName
567- | .inaccessible _ :: _ => processInaccessibleAsCtor alt subgoal.ctorName
568- | _ => unreachable!
569- return { mvarId := subgoal.mvarId, vars := newVars, alts := newAlts, examples := examples }
606+ -- withTraceNode `Meta.Match.match (msg := (return m!"{exceptEmoji ·} case {subgoal.ctorName}")) do
607+ if let some ctorName := subgoal.ctorName then
608+ -- A normal constructor case
609+ let subst := subgoal.subst
610+ let fields := subgoal.fields.toList
611+ let newVars := fields ++ xs
612+ let newVars := newVars.map fun x => x.applyFVarSubst subst
613+ let subex := Example.ctor ctorName <| fields.map fun field => match field with
614+ | .fvar fvarId => Example.var fvarId
615+ | _ => Example.underscore -- This case can happen due to dependent elimination
616+ let examples := p.examples.map <| Example.replaceFVarId x.fvarId! subex
617+ let examples := examples.map <| Example.applyFVarSubst subst
618+ let newAlts := p.alts.filter fun alt => match alt.patterns with
619+ | .ctor n .. :: _ => n == ctorName
620+ | .var _ :: _ => true
621+ | .inaccessible _ :: _ => true
622+ | _ => false
623+ let newAlts := newAlts.map fun alt => alt.applyFVarSubst subst
624+ let newAlts ← newAlts.mapM fun alt => do
625+ match alt.patterns with
626+ | .ctor _ _ _ fields :: ps => return { alt with patterns := fields ++ ps }
627+ | .var _ :: _ => expandVarIntoCtor alt ctorName
628+ | .inaccessible _ :: _ => processInaccessibleAsCtor alt ctorName
629+ | _ => unreachable!
630+ return { mvarId := subgoal.mvarId, vars := newVars, alts := newAlts, examples := examples }
631+ else
632+ -- A catch-all case
633+ let subst := subgoal.subst
634+ trace[Meta.Match.match] "constructor catch-all case"
635+ let examples := p.examples.map <| Example.applyFVarSubst subst
636+ let newVars := p.vars.map fun x => x.applyFVarSubst subst
637+ let newAlts := p.alts.filter fun alt => match alt.patterns with
638+ | .ctor .. :: _ => false
639+ | _ => true
640+ let newAlts := newAlts.map fun alt => alt.applyFVarSubst subst
641+ return { mvarId := subgoal.mvarId, alts := newAlts, vars := newVars, examples := examples }
570642
571643private def processNonVariable (p : Problem) : MetaM Problem := withGoalOf p do
572644 let x :: xs := p.vars | unreachable!
@@ -808,6 +880,15 @@ def processExFalso (p : Problem) : MetaM Problem := do
808880 let mvarId' ← p.mvarId.exfalso
809881 return { p with mvarId := mvarId' }
810882
883+ private def tracedForM (xs : Array α) (process : α → StateRefT State MetaM Unit) : StateRefT State MetaM Unit :=
884+ if xs.size > 1 then
885+ for x in xs, i in [:xs.size] do
886+ withTraceNode `Meta.Match.match (msg := (return m!"{exceptEmoji ·} subgoal {i+1}/{xs.size}" )) do
887+ process x
888+ else
889+ for x in xs do
890+ process x
891+
811892private partial def process (p : Problem) : StateRefT State MetaM Unit := do
812893 traceState p
813894 if isDone p then
@@ -870,7 +951,7 @@ private partial def process (p : Problem) : StateRefT State MetaM Unit := do
870951
871952 if (← isConstructorTransition p) then
872953 let ps ← processConstructor p
873- ps.forM process
954+ tracedForM ps process
874955 return
875956
876957 if isVariableTransition p then
@@ -881,12 +962,12 @@ private partial def process (p : Problem) : StateRefT State MetaM Unit := do
881962
882963 if isValueTransition p then
883964 let ps ← processValue p
884- ps.forM process
965+ tracedForM ps process
885966 return
886967
887968 if isArrayLitTransition p then
888969 let ps ← processArrayLit p
889- ps.forM process
970+ tracedForM ps process
890971 return
891972
892973 if (← hasNatValPattern p) then
0 commit comments