Skip to content

Commit e2f5938

Browse files
authored
refactor: some Meta.Match.Match refactorings (#11011)
This PR extracts some refactorings from #10763, including dropping dead code and not failing in `inaccessibleAsCtor`, which leadas to (slightly) better error messages, and also on the grounds that the failing alternative may actually be unreachable.
1 parent d3c9056 commit e2f5938

File tree

8 files changed

+647
-464
lines changed

8 files changed

+647
-464
lines changed

src/Lean/Meta/Match/Basic.lean

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -195,59 +195,6 @@ def replaceFVarId (fvarId : FVarId) (v : Expr) (alt : Alt) : Alt :=
195195
def isLocalDecl (fvarId : FVarId) (alt : Alt) : Bool :=
196196
alt.fvarDecls.any fun d => d.fvarId == fvarId
197197

198-
/--
199-
Similar to `checkAndReplaceFVarId`, but ensures type of `v` is definitionally equal to type of `fvarId`.
200-
This extra check is necessary when performing dependent elimination and inaccessible terms have been used.
201-
For example, consider the following code fragment:
202-
203-
```
204-
inductive Vec (α : Type u) : Nat → Type u where
205-
| nil : Vec α 0
206-
| cons {n} (head : α) (tail : Vec α n) : Vec α (n+1)
207-
208-
inductive VecPred {α : Type u} (P : α → Prop) : {n : Nat} → Vec α n → Prop where
209-
| nil : VecPred P Vec.nil
210-
| cons {n : Nat} {head : α} {tail : Vec α n} : P head → VecPred P tail → VecPred P (Vec.cons head tail)
211-
212-
theorem ex {α : Type u} (P : α → Prop) : {n : Nat} → (v : Vec α (n+1)) → VecPred P v → Exists P
213-
| _, Vec.cons head _, VecPred.cons h (w : VecPred P Vec.nil) => ⟨head, h⟩
214-
```
215-
Recall that `_` in a pattern can be elaborated into pattern variable or an inaccessible term.
216-
The elaborator uses an inaccessible term when typing constraints restrict its value.
217-
Thus, in the example above, the `_` at `Vec.cons head _` becomes the inaccessible pattern `.(Vec.nil)`
218-
because the type ascription `(w : VecPred P Vec.nil)` propagates typing constraints that restrict its value to be `Vec.nil`.
219-
After elaboration the alternative becomes:
220-
```
221-
| .(0), @Vec.cons .(α) .(0) head .(Vec.nil), @VecPred.cons .(α) .(P) .(0) .(head) .(Vec.nil) h w => ⟨head, h⟩
222-
```
223-
where
224-
```
225-
(head : α), (h: P head), (w : VecPred P Vec.nil)
226-
```
227-
Then, when we process this alternative in this module, the following check will detect that
228-
`w` has type `VecPred P Vec.nil`, when it is supposed to have type `VecPred P tail`.
229-
Note that if we had written
230-
```
231-
theorem ex {α : Type u} (P : α → Prop) : {n : Nat} → (v : Vec α (n+1)) → VecPred P v → Exists P
232-
| _, Vec.cons head Vec.nil, VecPred.cons h (w : VecPred P Vec.nil) => ⟨head, h⟩
233-
```
234-
we would get the easier to digest error message
235-
```
236-
missing cases:
237-
_, (Vec.cons _ _ (Vec.cons _ _ _)), _
238-
```
239-
-/
240-
def checkAndReplaceFVarId (fvarId : FVarId) (v : Expr) (alt : Alt) : MetaM Alt := do
241-
match alt.fvarDecls.find? fun (fvarDecl : LocalDecl) => fvarDecl.fvarId == fvarId with
242-
| none => throwErrorAt alt.ref "Unknown free pattern variable"
243-
| some fvarDecl => do
244-
let vType ← inferType v
245-
unless (← isDefEqGuarded fvarDecl.type vType) do
246-
withExistingLocalDecls alt.fvarDecls do
247-
let (expectedType, givenType) ← addPPExplicitToExposeDiff vType fvarDecl.type
248-
throwErrorAt alt.ref "Type mismatch during dependent match-elimination at pattern variable `{mkFVar fvarDecl.fvarId}` with type{indentExpr givenType}\nExpected type{indentExpr expectedType}"
249-
return replaceFVarId fvarId v alt
250-
251198
end Alt
252199

253200
inductive Example where

src/Lean/Meta/Match/Match.lean

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,32 @@ private def isVariableTransition (p : Problem) : Bool :=
159159
| .var _ :: _ => true
160160
| _ => false
161161

162-
private def isConstructorTransition (p : Problem) : Bool :=
163-
(hasCtorPattern p || p.alts.isEmpty)
164-
&& p.alts.all fun alt => match alt.patterns with
165-
| .ctor .. :: _ => true
166-
| .var _ :: _ => true
167-
| .inaccessible _ :: _ => true
168-
| _ => false
162+
private def getInductiveVal? (x : Expr) : MetaM (Option InductiveVal) := do
163+
let xType ← inferType x
164+
let xType ← whnfD xType
165+
match xType.getAppFn with
166+
| Expr.const constName _ =>
167+
let cinfo ← getConstInfo constName
168+
match cinfo with
169+
| ConstantInfo.inductInfo val => return some val
170+
| _ => return none
171+
| _ => return none
172+
173+
def isCurrVarInductive (p : Problem) : MetaM Bool := do
174+
match p.vars with
175+
| [] => return false
176+
| x::_ => withGoalOf p do
177+
let val? ← getInductiveVal? x
178+
return val?.isSome
179+
180+
private def isConstructorTransition (p : Problem) : MetaM Bool := do
181+
return (← isCurrVarInductive p)
182+
&& (hasCtorPattern p || p.alts.isEmpty)
183+
&& p.alts.all fun alt => match alt.patterns with
184+
| .ctor .. :: _ => true
185+
| .var _ :: _ => true
186+
| .inaccessible _ :: _ => true
187+
| _ => false
169188

170189
private def isValueTransition (p : Problem) : Bool :=
171190
hasVarPattern p && hasValPattern p
@@ -204,8 +223,7 @@ private def hasCtorOrInaccessible (p : Problem) : Bool :=
204223
| _ => false
205224

206225
private def isNatValueTransition (p : Problem) : MetaM Bool := do
207-
unless (← hasNatValPattern p) do return false
208-
return hasCtorOrInaccessible p
226+
return (← hasNatValPattern p) && hasCtorOrInaccessible p
209227

210228
/--
211229
Predicate for testing whether we need to expand `Int` value patterns into constructors.
@@ -249,6 +267,10 @@ private def reorientCnstrs (alt : Alt) : Alt :=
249267
/--
250268
Remove constraints of the form `lhs ≋ rhs` where `lhs` and `rhs` are definitionally equal,
251269
or `lhs` is a free variable.
270+
271+
Dropping unsolved constraints where `lhs` is a free variable seems unsound, but simply leads to later
272+
errors about the type of the alternative not matching the goal type, which is arguably a bit more
273+
user-friendly than showing possibly match-compilation-internal variable names.
252274
-/
253275
private def filterTrivialCnstrs (alt : Alt) : MetaM Alt := do
254276
let cnstrs ← withExistingLocalDecls alt.fvarDecls do
@@ -334,8 +356,9 @@ private def processAsPattern (p : Problem) : MetaM Problem := withGoalOf p do
334356
let alts ← p.alts.mapM fun alt => do
335357
match alt.patterns with
336358
| .as fvarId p h :: ps =>
337-
/- We used to use `checkAndReplaceFVarId` here, but `x` and `fvarId` may have different types
338-
when dependent types are being used. Let's consider the repro for issue #471
359+
/- We used to use eagerly check the types here (using what was called `checkAndReplaceFVarId`),
360+
but `x` and `fvarId` can have different types when dependent types are being used.
361+
Let's consider the repro for issue #471
339362
```
340363
inductive vec : Nat → Type
341364
| nil : vec 0
@@ -409,11 +432,11 @@ alternative `cnstrs` field.
409432
private def inLocalDecls (localDecls : List LocalDecl) (fvarId : FVarId) : Bool :=
410433
localDecls.any fun d => d.fvarId == fvarId
411434

412-
private def expandVarIntoCtor? (alt : Alt) (ctorName : Name) : MetaM Alt := do
435+
private def expandVarIntoCtor (alt : Alt) (ctorName : Name) : MetaM Alt := do
413436
let .var fvarId :: ps := alt.patterns | unreachable!
414437
let alt := { alt with patterns := ps}
415438
withExistingLocalDecls alt.fvarDecls do
416-
trace[Meta.Match.unify] "expandVarIntoCtor? fvarId: {mkFVar fvarId}, ctorName: {ctorName}, alt:\n{← alt.toMessageData}"
439+
trace[Meta.Match.unify] "expandVarIntoCtor fvarId: {mkFVar fvarId}, ctorName: {ctorName}, alt:\n{← alt.toMessageData}"
417440
let expectedType ← inferType (mkFVar fvarId)
418441
let expectedType ← whnfD expectedType
419442
let (ctorLevels, ctorParams) ← getInductiveUniverseAndParams expectedType
@@ -427,7 +450,7 @@ private def expandVarIntoCtor? (alt : Alt) (ctorName : Name) : MetaM Alt := do
427450
let mut cnstrs := alt.cnstrs
428451
unless (← isDefEqGuarded resultType expectedType) do
429452
cnstrs := (resultType, expectedType) :: cnstrs
430-
trace[Meta.Match.unify] "expandVarIntoCtor? {mkFVar fvarId} : {expectedType}, ctor: {ctor}"
453+
trace[Meta.Match.unify] "expandVarIntoCtor {mkFVar fvarId} : {expectedType}, ctor: {ctor}"
431454
let ctorFieldPatterns := ctorFieldDecls.toList.map fun decl => Pattern.var decl.fvarId
432455
return { alt with fvarDecls := newAltDecls, patterns := ctorFieldPatterns ++ alt.patterns, cnstrs }
433456

@@ -443,46 +466,31 @@ private def expandInaccessibleIntoVar (alt : Alt) : MetaM Alt := do
443466
cnstrs := (x, e) :: alt.cnstrs
444467
}
445468

446-
private def getInductiveVal? (x : Expr) : MetaM (Option InductiveVal) := do
447-
let xType ← inferType x
448-
let xType ← whnfD xType
449-
match xType.getAppFn with
450-
| Expr.const constName _ =>
451-
let cinfo ← getConstInfo constName
452-
match cinfo with
453-
| ConstantInfo.inductInfo val => return some val
454-
| _ => return none
455-
| _ => return none
456-
457469
private def hasRecursiveType (x : Expr) : MetaM Bool := do
458470
match (← getInductiveVal? x) with
459471
| some val => return val.isRec
460472
| _ => return false
461473

462474
/-- Given `alt` s.t. the next pattern is an inaccessible pattern `e`,
463475
try to normalize `e` into a constructor application.
464-
If it is not a constructor, throw an error.
465-
Otherwise, if it is a constructor application of `ctorName`,
476+
If it is a constructor application of `ctorName`,
466477
update the next patterns with the fields of the constructor.
467478
Otherwise, move it to contraints, so that we fail unless some later step
468479
eliminates this alternative.
469480
-/
470481
def processInaccessibleAsCtor (alt : Alt) (ctorName : Name) : MetaM Alt := do
471-
let p@(.inaccessible e) :: ps := alt.patterns | unreachable!
472-
trace[Meta.Match.match] "inaccessible in ctor step {e}"
482+
let .inaccessible e :: ps := alt.patterns | unreachable!
483+
trace[Meta.Match.match] "inaccessible step {e} as ctor {ctorName}"
473484
withExistingLocalDecls alt.fvarDecls do
474485
-- Try to push inaccessible annotations.
475486
let e ← whnfD e
476-
match (← constructorApp? e) with
477-
| some (ctorVal, ctorArgs) =>
487+
if let some (ctorVal, ctorArgs) ← constructorApp? e then
478488
if ctorVal.name == ctorName then
479489
let fields := ctorArgs.extract ctorVal.numParams ctorArgs.size
480490
let fields := fields.toList.map .inaccessible
481491
return { alt with patterns := fields ++ ps }
482-
else
483-
let alt' ← expandInaccessibleIntoVar alt
484-
expandVarIntoCtor? alt' ctorName
485-
| _ => throwErrorAt alt.ref "Dependent match elimination failed: Expected a constructor, but found the inaccessible pattern{indentD p.toMessageData}"
492+
let alt' ← expandInaccessibleIntoVar alt
493+
expandVarIntoCtor alt' ctorName
486494

487495
private def hasNonTrivialExample (p : Problem) : Bool :=
488496
p.examples.any fun | Example.underscore => false | _ => true
@@ -546,7 +554,7 @@ private def processConstructor (p : Problem) : MetaM (Array Problem) := do
546554
let newAlts ← newAlts.mapM fun alt => do
547555
match alt.patterns with
548556
| .ctor _ _ _ fields :: ps => return { alt with patterns := fields ++ ps }
549-
| .var _ :: _ => expandVarIntoCtor? alt subgoal.ctorName
557+
| .var _ :: _ => expandVarIntoCtor alt subgoal.ctorName
550558
| .inaccessible _ :: _ => processInaccessibleAsCtor alt subgoal.ctorName
551559
| _ => unreachable!
552560
return { mvarId := subgoal.mvarId, vars := newVars, alts := newAlts, examples := examples }
@@ -563,7 +571,7 @@ private def processNonVariable (p : Problem) : MetaM Problem := withGoalOf p do
563571
else
564572
return some { alt with patterns := fields ++ ps }
565573
| .inaccessible _ :: _ => processInaccessibleAsCtor alt ctorVal.name
566-
| .var _ :: _ => expandVarIntoCtor? alt ctorVal.name
574+
| .var _ :: _ => expandVarIntoCtor alt ctorVal.name
567575
| _ => unreachable!
568576
let xFields := xArgs.extract ctorVal.numParams xArgs.size
569577
return { p with alts := alts, vars := xFields.toList ++ xs }
@@ -724,13 +732,6 @@ private def throwNonSupported (p : Problem) : MetaM Unit :=
724732
let msg ← p.toMessageData
725733
throwError "Failed to compile pattern matching: Stuck at{indentD msg}"
726734

727-
def isCurrVarInductive (p : Problem) : MetaM Bool := do
728-
match p.vars with
729-
| [] => return false
730-
| x::_ => withGoalOf p do
731-
let val? ← getInductiveVal? x
732-
return val?.isSome
733-
734735
private def checkNextPatternTypes (p : Problem) : MetaM Unit := do
735736
match p.vars with
736737
| [] => return ()
@@ -760,55 +761,79 @@ def processExFalso (p : Problem) : MetaM Problem := do
760761

761762
private partial def process (p : Problem) : StateRefT State MetaM Unit := do
762763
traceState p
763-
let isInductive ← isCurrVarInductive p
764764
if isDone p then
765765
traceStep ("leaf")
766766
processLeaf p
767-
else if (← isExFalsoTransition p) then
767+
return
768+
769+
if (← isExFalsoTransition p) then
768770
traceStep ("ex falso")
769771
let p ← processExFalso p
770772
process p
771-
else if hasAsPattern p then
773+
return
774+
775+
if hasAsPattern p then
772776
traceStep ("as-pattern")
773777
let p ← processAsPattern p
774778
process p
775-
else if (← isNatValueTransition p) then
779+
return
780+
781+
if (← isNatValueTransition p) then
776782
traceStep ("nat value to constructor")
777783
process (← expandNatValuePattern p)
778-
else if (← isIntValueTransition p) then
784+
return
785+
786+
if (← isIntValueTransition p) then
779787
traceStep ("int value to constructor")
780788
process (← expandIntValuePattern p)
781-
else if (← isFinValueTransition p) then
789+
return
790+
791+
if (← isFinValueTransition p) then
782792
traceStep ("fin value to constructor")
783793
process (← expandFinValuePattern p)
784-
else if (← isBitVecValueTransition p) then
794+
return
795+
796+
if (← isBitVecValueTransition p) then
785797
traceStep ("bitvec value to constructor")
786798
process (← expandBitVecValuePattern p)
787-
else if !isNextVar p then
799+
return
800+
801+
if !isNextVar p then
788802
traceStep ("non variable")
789803
let p ← processNonVariable p
790804
process p
791-
else if isInductive && isConstructorTransition p then
805+
return
806+
807+
if (← isConstructorTransition p) then
792808
let ps ← processConstructor p
793809
ps.forM process
794-
else if isVariableTransition p then
810+
return
811+
812+
if isVariableTransition p then
795813
traceStep ("variable")
796814
let p ← processVariable p
797815
process p
798-
else if isValueTransition p then
816+
return
817+
818+
if isValueTransition p then
799819
let ps ← processValue p
800820
ps.forM process
801-
else if isArrayLitTransition p then
821+
return
822+
823+
if isArrayLitTransition p then
802824
let ps ← processArrayLit p
803825
ps.forM process
804-
else if (← hasNatValPattern p) then
826+
return
827+
828+
if (← hasNatValPattern p) then
805829
-- This branch is reachable when `p`, for example, is just values without an else-alternative.
806830
-- We added it just to get better error messages.
807831
traceStep ("nat value to constructor")
808832
process (← expandNatValuePattern p)
809-
else
810-
checkNextPatternTypes p
811-
throwNonSupported p
833+
return
834+
835+
checkNextPatternTypes p
836+
throwNonSupported p
812837

813838
private def getUElimPos? (matcherLevels : List Level) (uElim : Level) : MetaM (Option Nat) :=
814839
if uElim == levelZero then

0 commit comments

Comments
 (0)