/- Copyright (c) 2020 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura -/ import Lean.Meta.Match.MatchPatternAttr import Lean.Meta.Match.Match import Lean.Elab.SyntheticMVars import Lean.Elab.App import Lean.Parser.Term namespace Lean.Elab.Term open Meta open Lean.Parser.Term /- This modules assumes "match"-expressions use the following syntax. ```lean def matchDiscr := parser! optional (try (ident >> checkNoWsBefore "no space before ':'" >> ":")) >> termParser def «match» := parser!:leadPrec "match " >> sepBy1 matchDiscr ", " >> optType >> " with " >> matchAlts ``` -/ structure MatchAltView where ref : Syntax patterns : Array Syntax rhs : Syntax deriving Inhabited private def expandSimpleMatch (stx discr lhsVar rhs : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do let newStx ← `(let $lhsVar := $discr; $rhs) withMacroExpansion stx newStx $ elabTerm newStx expectedType? private def expandSimpleMatchWithType (stx discr lhsVar type rhs : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do let newStx ← `(let $lhsVar : $type := $discr; $rhs) withMacroExpansion stx newStx $ elabTerm newStx expectedType? private def elabDiscrsWitMatchType (discrStxs : Array Syntax) (matchType : Expr) (expectedType : Expr) : TermElabM (Array Expr) := do let mut discrs := #[] let mut i := 0 let mut matchType := matchType for discrStx in discrStxs do i := i + 1 matchType ← whnf matchType match matchType with | Expr.forallE _ d b _ => let discr ← fullApproxDefEq $ elabTermEnsuringType discrStx[1] d trace[Elab.match]! "discr #{i} {discr} : {d}" matchType ← b.instantiate1 discr discrs := discrs.push discr | _ => throwError! "invalid type provided to match-expression, function type with arity #{discrStxs.size} expected" pure discrs private def mkUserNameFor (e : Expr) : TermElabM Name := match e with | Expr.fvar fvarId _ => do pure (← getLocalDecl fvarId).userName | _ => mkFreshBinderName -- `expandNonAtomicDiscrs?` create auxiliary variables with base name `_discr` private def isAuxDiscrName (n : Name) : Bool := n.eraseMacroScopes == `_discr -- See expandNonAtomicDiscrs? private def elabAtomicDiscr (discr : Syntax) : TermElabM Expr := do let term := discr[1] match (← isLocalIdent? term) with | some e@(Expr.fvar fvarId _) => let localDecl ← getLocalDecl fvarId if !isAuxDiscrName localDecl.userName then pure e -- it is not an auxiliary local created by `expandNonAtomicDiscrs?` else pure localDecl.value | _ => throwErrorAt discr "unexpected discriminant" private def elabMatchTypeAndDiscrs (discrStxs : Array Syntax) (matchOptType : Syntax) (matchAltViews : Array MatchAltView) (expectedType : Expr) : TermElabM (Array Expr × Expr × Array MatchAltView) := do let numDiscrs := discrStxs.size if matchOptType.isNone then let rec loop (i : Nat) (discrs : Array Expr) (matchType : Expr) (matchAltViews : Array MatchAltView) := do match i with | 0 => pure (discrs.reverse, matchType, matchAltViews) | i+1 => let discrStx := discrStxs[i] let discr ← elabAtomicDiscr discrStx let discr ← instantiateMVars discr let discrType ← inferType discr let discrType ← instantiateMVars discrType let matchTypeBody ← kabstract matchType discr let userName ← mkUserNameFor discr if discrStx[0].isNone then loop i (discrs.push discr) (Lean.mkForall userName BinderInfo.default discrType matchTypeBody) matchAltViews else let identStx := discrStx[0][0] withLocalDeclD userName discrType fun x => do let eqType ← mkEq discr x withLocalDeclD identStx.getId eqType fun h => do let matchTypeBody := matchTypeBody.instantiate1 x let matchType ← mkForallFVars #[x, h] matchTypeBody let refl ← mkEqRefl discr let discrs := (discrs.push refl).push discr let matchAltViews := matchAltViews.map fun altView => { altView with patterns := altView.patterns.insertAt (i+1) identStx } loop i discrs matchType matchAltViews loop discrStxs.size #[] expectedType matchAltViews else let matchTypeStx := matchOptType[0][1] let matchType ← elabType matchTypeStx let discrs ← elabDiscrsWitMatchType discrStxs matchType expectedType pure (discrs, matchType, matchAltViews) def expandMacrosInPatterns (matchAlts : Array MatchAltView) : MacroM (Array MatchAltView) := do matchAlts.mapM fun matchAlt => do let patterns ← matchAlt.patterns.mapM expandMacros pure { matchAlt with patterns := patterns } /- Given `stx` a match-expression, return its alternatives. -/ private def getMatchAlts : Syntax → Array MatchAltView | `(match $discrs,* $[: $ty?]? with $alts:matchAlt*) => alts.map fun alt => match alt with | `(matchAltExpr| | $patterns,* => $rhs) => { ref := alt, patterns := patterns, rhs := rhs } | _ => unreachable! | _ => unreachable! /-- Auxiliary annotation used to mark terms marked with the "inaccessible" annotation `.(t)` and `_` in patterns. -/ def mkInaccessible (e : Expr) : Expr := mkAnnotation `_inaccessible e def inaccessible? (e : Expr) : Option Expr := annotation? `_inaccessible e inductive PatternVar where | localVar (userName : Name) -- anonymous variables (`_`) are encoded using metavariables | anonymousVar (mvarId : MVarId) instance : ToString PatternVar := ⟨fun | PatternVar.localVar x => toString x | PatternVar.anonymousVar mvarId => s!"?m{mvarId}"⟩ builtin_initialize Parser.registerBuiltinNodeKind `MVarWithIdKind /-- Create an auxiliary Syntax node wrapping a fresh metavariable id. We use this kind of Syntax for representing `_` occurring in patterns. The metavariables are created before we elaborate the patterns into `Expr`s. -/ private def mkMVarSyntax : TermElabM Syntax := do let mvarId ← mkFreshId pure $ Syntax.node `MVarWithIdKind #[Syntax.node mvarId #[]] /-- Given a syntax node constructed using `mkMVarSyntax`, return its MVarId -/ private def getMVarSyntaxMVarId (stx : Syntax) : MVarId := stx[0].getKind /-- The elaboration function for `Syntax` created using `mkMVarSyntax`. It just converts the metavariable id wrapped by the Syntax into an `Expr`. -/ @[builtinTermElab MVarWithIdKind] def elabMVarWithIdKind : TermElab := fun stx expectedType? => pure $ mkInaccessible $ mkMVar (getMVarSyntaxMVarId stx) @[builtinTermElab inaccessible] def elabInaccessible : TermElab := fun stx expectedType? => do let e ← elabTerm stx[1] expectedType? pure $ mkInaccessible e /- Patterns define new local variables. This module collect them and preprocess `_` occurring in patterns. Recall that an `_` may represent anonymous variables or inaccessible terms that are implied by typing constraints. Thus, we represent them with fresh named holes `?x`. After we elaborate the pattern, if the metavariable remains unassigned, we transform it into a regular pattern variable. Otherwise, it becomes an inaccessible term. Macros occurring in patterns are expanded before the `collectPatternVars` method is executed. The following kinds of Syntax are handled by this module - Constructor applications - Applications of functions tagged with the `[matchPattern]` attribute - Identifiers - Anonymous constructors - Structure instances - Inaccessible terms - Named patterns - Tuple literals - Type ascriptions - Literals: num, string and char -/ namespace CollectPatternVars structure State where found : NameSet := {} vars : Array PatternVar := #[] abbrev M := StateRefT State TermElabM private def throwCtorExpected {α} : M α := throwError "invalid pattern, constructor or constant marked with '[matchPattern]' expected" private def getNumExplicitCtorParams (ctorVal : ConstructorVal) : TermElabM Nat := forallBoundedTelescope ctorVal.type ctorVal.nparams fun ps _ => do let mut result := 0 for p in ps do let localDecl ← getLocalDecl p.fvarId! if localDecl.binderInfo.isExplicit then result := result+1 pure result private def throwInvalidPattern {α} : M α := throwError "invalid pattern" namespace CtorApp /- An application in a pattern can be 1- A constructor application The elaborator assumes fields are accessible and inductive parameters are not accessible. 2- A regular application `(f ...)` where `f` is tagged with `[matchPattern]`. The elaborator assumes implicit arguments are not accessible and explicit ones are accessible. -/ structure Context where funId : Syntax ctorVal? : Option ConstructorVal -- It is `some`, if constructor application explicit : Bool ellipsis : Bool paramDecls : Array LocalDecl paramDeclIdx : Nat := 0 namedArgs : Array NamedArg args : List Arg newArgs : Array Syntax := #[] deriving Inhabited private def isDone (ctx : Context) : Bool := ctx.paramDeclIdx ≥ ctx.paramDecls.size private def finalize (ctx : Context) : M Syntax := do if ctx.namedArgs.isEmpty && ctx.args.isEmpty then let fStx ← `(@$(ctx.funId):ident) pure $ Syntax.mkApp fStx ctx.newArgs else throwError "too many arguments" private def isNextArgAccessible (ctx : Context) : Bool := let i := ctx.paramDeclIdx match ctx.ctorVal? with | some ctorVal => i ≥ ctorVal.nparams -- For constructor applications only fields are accessible | none => if h : i < ctx.paramDecls.size then -- For `[matchPattern]` applications, only explicit parameters are accessible. let d := ctx.paramDecls.get ⟨i, h⟩ d.binderInfo.isExplicit else false private def getNextParam (ctx : Context) : LocalDecl × Context := let i := ctx.paramDeclIdx let d := ctx.paramDecls[i] (d, { ctx with paramDeclIdx := ctx.paramDeclIdx + 1 }) private def pushNewArg (collect : Syntax → M Syntax) (accessible : Bool) (ctx : Context) (arg : Arg) : M Context := match arg with | Arg.stx stx => do let stx ← if accessible then collect stx else pure stx pure { ctx with newArgs := ctx.newArgs.push stx } | _ => unreachable! private def processExplicitArg (collect : Syntax → M Syntax) (accessible : Bool) (ctx : Context) : M Context := match ctx.args with | [] => if ctx.ellipsis then do let hole ← `(_) pushNewArg collect accessible ctx (Arg.stx hole) else throwError! "explicit parameter is missing, unused named arguments {ctx.namedArgs.map fun narg => narg.name}" | arg::args => do let ctx := { ctx with args := args } pushNewArg collect accessible ctx arg private def processImplicitArg (collect : Syntax → M Syntax) (accessible : Bool) (ctx : Context) : M Context := if ctx.explicit then processExplicitArg collect accessible ctx else do let hole ← `(_) pushNewArg collect accessible ctx (Arg.stx hole) private partial def processCtorAppAux (collect : Syntax → M Syntax) (ctx : Context) : M Syntax := do if isDone ctx then finalize ctx else let accessible := isNextArgAccessible ctx let (d, ctx) := getNextParam ctx match ctx.namedArgs.findIdx? fun namedArg => namedArg.name == d.userName with | some idx => let arg := ctx.namedArgs[idx] let ctx := { ctx with namedArgs := ctx.namedArgs.eraseIdx idx } let ctx ← pushNewArg collect accessible ctx arg.val processCtorAppAux collect ctx | none => let ctx ← match d.binderInfo with | BinderInfo.implicit => processImplicitArg collect accessible ctx | BinderInfo.instImplicit => processImplicitArg collect accessible ctx | _ => processExplicitArg collect accessible ctx processCtorAppAux collect ctx def processCtorApp (collect : Syntax → M Syntax) (f : Syntax) (namedArgs : Array NamedArg) (args : Array Arg) (ellipsis : Bool) : M Syntax := do let args := args.toList let (fId, explicit) ← match f with | `($fId:ident) => pure (fId, false) | `(@$fId:ident) => pure (fId, true) | _ => throwError "identifier expected" let some (Expr.const fName _ _) ← resolveId? fId "pattern" | throwCtorExpected let fInfo ← getConstInfo fName forallTelescopeReducing fInfo.type fun xs _ => do let paramDecls ← xs.mapM (getFVarLocalDecl ·) match fInfo with | ConstantInfo.ctorInfo val => processCtorAppAux collect { funId := fId, explicit := explicit, ctorVal? := val, paramDecls := paramDecls, namedArgs := namedArgs, args := args, ellipsis := ellipsis } | _ => let env ← getEnv if hasMatchPatternAttribute env fName then processCtorAppAux collect { funId := fId, explicit := explicit, ctorVal? := none, paramDecls := paramDecls, namedArgs := namedArgs, args := args, ellipsis := ellipsis } else throwCtorExpected end CtorApp def processCtorApp (collect : Syntax → M Syntax) (stx : Syntax) : M Syntax := do let (f, namedArgs, args, ellipsis) ← liftM $ expandApp stx true CtorApp.processCtorApp collect f namedArgs args ellipsis def processCtor (collect : Syntax → M Syntax) (stx : Syntax) : M Syntax := do CtorApp.processCtorApp collect stx #[] #[] false private def processVar (idStx : Syntax) : M Syntax := do unless idStx.isIdent do throwErrorAt idStx "identifier expected" let id := idStx.getId unless id.eraseMacroScopes.isAtomic do throwError "invalid pattern variable, must be atomic" let s ← get if s.found.contains id then throwError! "invalid pattern, variable '{id}' occurred more than once" modify fun s => { s with vars := s.vars.push (PatternVar.localVar id), found := s.found.insert id } pure idStx /- Check whether `stx` is a pattern variable or constructor-like (i.e., constructor or constant tagged with `[matchPattern]` attribute) -/ private def processId (collect : Syntax → M Syntax) (stx : Syntax) : M Syntax := do let env ← getEnv match (← resolveId? stx "pattern") with | none => processVar stx | some f => match f with | Expr.const fName _ _ => match env.find? fName with | some (ConstantInfo.ctorInfo _) => processCtor collect stx | some _ => if hasMatchPatternAttribute env fName then processCtor collect stx else processVar stx | none => throwCtorExpected | _ => processVar stx private def nameToPattern : Name → TermElabM Syntax | Name.anonymous => `(Name.anonymous) | Name.str p s _ => do let p ← nameToPattern p; `(Name.str $p $(quote s) _) | Name.num p n _ => do let p ← nameToPattern p; `(Name.num $p $(quote n) _) private def quotedNameToPattern (stx : Syntax) : TermElabM Syntax := match stx[0].isNameLit? with | some val => nameToPattern val | none => throwIllFormedSyntax partial def collect : Syntax → M Syntax | stx@(Syntax.node k args) => withRef stx $ withFreshMacroScope do if k == `Lean.Parser.Term.app then processCtorApp collect stx else if k == `Lean.Parser.Term.anonymousCtor then let elems ← args[1].getArgs.mapSepElemsM collect pure $ Syntax.node k (args.set! 1 $ mkNullNode elems) else if k == `Lean.Parser.Term.structInst then /- ``` parser! "{" >> optional (atomic (termParser >> " with ")) >> manyIndent (group (structInstField >> optional ", ")) >> optional ".." >> optional (" : " >> termParser) >> " }" ``` -/ let withMod := args[1] unless withMod.isNone do throwErrorAt withMod "invalid struct instance pattern, 'with' is not allowed in patterns" let fields ← args[2].getArgs.mapM fun p => do -- p is of the form (group (structInstField >> optional ", ")) let field := p[0] -- parser! structInstLVal >> " := " >> termParser let newVal ← collect field[2] let field := field.setArg 2 newVal pure <| field.setArg 0 field pure <| Syntax.node k (args.set! 2 <| mkNullNode fields) else if k == `Lean.Parser.Term.hole then let r ← mkMVarSyntax modify fun s => { s with vars := s.vars.push $ PatternVar.anonymousVar $ getMVarSyntaxMVarId r } pure r else if k == `Lean.Parser.Term.paren then let arg := args[1] if arg.isNone then pure stx -- `()` else let t := arg[0] let s := arg[1] if s.isNone || s[0].getKind == `Lean.Parser.Term.typeAscription then -- Ignore `s`, since it empty or it is a type ascription let t ← collect t let arg := arg.setArg 0 t pure $ Syntax.node k (args.set! 1 arg) else -- Tuple literal is a constructor let t ← collect t let arg := arg.setArg 0 t let tupleTail := s[0] let tupleTailElems := tupleTail[1].getArgs let tupleTailElems ← tupleTailElems.mapSepElemsM collect let tupleTail := tupleTail.setArg 1 $ mkNullNode tupleTailElems let s := s.setArg 0 tupleTail let arg := arg.setArg 1 s pure $ Syntax.node k (args.set! 1 arg) else if k == `Lean.Parser.Term.explicitUniv then processCtor collect stx[0] else if k == `Lean.Parser.Term.namedPattern then /- Recall that def namedPattern := check... >> tparser! "@" >> termParser -/ let id := stx[0] discard <| processVar id let pat := stx[2] let pat ← collect pat `(_root_.namedPattern $id $pat) else if k == `Lean.Parser.Term.inaccessible then pure stx else if k == strLitKind then pure stx else if k == numLitKind then pure stx else if k == scientificLitKind then pure stx else if k == charLitKind then pure stx else if k == `Lean.Parser.Term.quotedName then /- Quoted names have an elaboration function associated with them, and they will not be macro expanded. Note that macro expansion is not a good option since it produces a term using the smart constructors `Name.mkStr`, `Name.mkNum` instead of the constructors `Name.str` and `Name.num` -/ quotedNameToPattern stx else if k == choiceKind then throwError "invalid pattern, notation is ambiguous" else throwInvalidPattern | stx@(Syntax.ident _ _ _ _) => processId collect stx | stx => throwInvalidPattern def main (alt : MatchAltView) : M MatchAltView := do let patterns ← alt.patterns.mapM fun p => do trace[Elab.match]! "collecting variables at pattern: {p}" collect p pure { alt with patterns := patterns } end CollectPatternVars private def collectPatternVars (alt : MatchAltView) : TermElabM (Array PatternVar × MatchAltView) := do let (alt, s) ← (CollectPatternVars.main alt).run {} pure (s.vars, alt) /- Return the pattern variables in the given pattern. Remark: this method is not used here, but in other macros (e.g., at `Do.lean`). -/ def getPatternVars (patternStx : Syntax) : TermElabM (Array PatternVar) := do let patternStx ← liftMacroM $ expandMacros patternStx let (_, s) ← (CollectPatternVars.collect patternStx).run {} pure s.vars def getPatternsVars (patterns : Array Syntax) : TermElabM (Array PatternVar) := do let collect : CollectPatternVars.M Unit := do for pattern in patterns do discard <| CollectPatternVars.collect (← liftMacroM $ expandMacros pattern) let (_, s) ← collect.run {} pure s.vars /- We convert the collected `PatternVar`s intro `PatternVarDecl` -/ inductive PatternVarDecl where /- For `anonymousVar`, we create both a metavariable and a free variable. The free variable is used as an assignment for the metavariable when it is not assigned during pattern elaboration. -/ | anonymousVar (mvarId : MVarId) (fvarId : FVarId) | localVar (fvarId : FVarId) private partial def withPatternVars {α} (pVars : Array PatternVar) (k : Array PatternVarDecl → TermElabM α) : TermElabM α := let rec loop (i : Nat) (decls : Array PatternVarDecl) := do if h : i < pVars.size then match pVars.get ⟨i, h⟩ with | PatternVar.anonymousVar mvarId => let type ← mkFreshTypeMVar let userName ← mkFreshBinderName withLocalDecl userName BinderInfo.default type fun x => loop (i+1) (decls.push (PatternVarDecl.anonymousVar mvarId x.fvarId!)) | PatternVar.localVar userName => let type ← mkFreshTypeMVar withLocalDecl userName BinderInfo.default type fun x => loop (i+1) (decls.push (PatternVarDecl.localVar x.fvarId!)) else /- We must create the metavariables for `PatternVar.anonymousVar` AFTER we create the new local decls using `withLocalDecl`. Reason: their scope must include the new local decls since some of them are assigned by typing constraints. -/ decls.forM fun decl => match decl with | PatternVarDecl.anonymousVar mvarId fvarId => do let type ← inferType (mkFVar fvarId) discard <| mkFreshExprMVarWithId mvarId type | _ => pure () k decls loop 0 #[] private def elabPatterns (patternStxs : Array Syntax) (matchType : Expr) : TermElabM (Array Expr × Expr) := do let mut patterns := #[] let mut matchType := matchType for patternStx in patternStxs do matchType ← whnf matchType match matchType with | Expr.forallE _ d b _ => let pattern ← elabTermEnsuringType patternStx d matchType := b.instantiate1 pattern patterns := patterns.push pattern | _ => throwError "unexpected match type" pure (patterns, matchType) def finalizePatternDecls (patternVarDecls : Array PatternVarDecl) : TermElabM (Array LocalDecl) := do let mut decls := #[] for pdecl in patternVarDecls do match pdecl with | PatternVarDecl.localVar fvarId => let decl ← getLocalDecl fvarId let decl ← instantiateLocalDeclMVars decl decls := decls.push decl | PatternVarDecl.anonymousVar mvarId fvarId => let e ← instantiateMVars (mkMVar mvarId); trace[Elab.match]! "finalizePatternDecls: mvarId: {mvarId} := {e}, fvar: {mkFVar fvarId}" match e with | Expr.mvar newMVarId _ => /- Metavariable was not assigned, or assigned to another metavariable. So, we assign to the auxiliary free variable we created at `withPatternVars` to `newMVarId`. -/ assignExprMVar newMVarId (mkFVar fvarId) trace[Elab.match]! "finalizePatternDecls: {mkMVar newMVarId} := {mkFVar fvarId}" let decl ← getLocalDecl fvarId let decl ← instantiateLocalDeclMVars decl decls := decls.push decl | _ => pure () pure decls open Meta.Match (Pattern Pattern.var Pattern.inaccessible Pattern.ctor Pattern.as Pattern.val Pattern.arrayLit AltLHS MatcherResult) namespace ToDepElimPattern structure State where found : NameSet := {} localDecls : Array LocalDecl newLocals : NameSet := {} abbrev M := StateRefT State TermElabM private def alreadyVisited (fvarId : FVarId) : M Bool := do let s ← get pure $ s.found.contains fvarId private def markAsVisited (fvarId : FVarId) : M Unit := modify fun s => { s with found := s.found.insert fvarId } private def throwInvalidPattern {α} (e : Expr) : M α := throwError! "invalid pattern {indentExpr e}" /- Create a new LocalDecl `x` for the metavariable `mvar`, and return `Pattern.var x` -/ private def mkLocalDeclFor (mvar : Expr) : M Pattern := do let mvarId := mvar.mvarId! let s ← get match (← getExprMVarAssignment? mvarId) with | some val => pure $ Pattern.inaccessible val | none => let fvarId ← mkFreshId let type ← inferType mvar /- HACK: `fvarId` is not in the scope of `mvarId` If this generates problems in the future, we should update the metavariable declarations. -/ assignExprMVar mvarId (mkFVar fvarId) let userName ← liftM $ mkFreshBinderName let newDecl := LocalDecl.cdecl arbitrary fvarId userName type BinderInfo.default; modify fun s => { s with newLocals := s.newLocals.insert fvarId, localDecls := match s.localDecls.findIdx? fun decl => mvar.occurs decl.type with | none => s.localDecls.push newDecl -- None of the existing declarations depend on `mvar` | some i => s.localDecls.insertAt i newDecl } pure $ Pattern.var fvarId partial def main (e : Expr) : M Pattern := do let isLocalDecl (fvarId : FVarId) : M Bool := do let s ← get pure $ s.localDecls.any fun d => d.fvarId == fvarId let mkPatternVar (fvarId : FVarId) (e : Expr) : M Pattern := do if (← alreadyVisited fvarId) then pure $ Pattern.inaccessible e else markAsVisited fvarId pure $ Pattern.var e.fvarId! let mkInaccessible (e : Expr) : M Pattern := do match e with | Expr.fvar fvarId _ => if (← isLocalDecl fvarId) then mkPatternVar fvarId e else pure $ Pattern.inaccessible e | _ => pure $ Pattern.inaccessible e match inaccessible? e with | some t => mkInaccessible t | none => match e.arrayLit? with | some (α, lits) => let ps ← lits.mapM main; pure $ Pattern.arrayLit α ps | none => if e.isAppOfArity `namedPattern 3 then let p ← main $ e.getArg! 2; match e.getArg! 1 with | Expr.fvar fvarId _ => pure $ Pattern.as fvarId p | _ => throwError "unexpected occurrence of auxiliary declaration 'namedPattern'" else if e.isNatLit || e.isStringLit || e.isCharLit then pure $ Pattern.val e else if e.isFVar then let fvarId := e.fvarId! unless(← isLocalDecl fvarId) do throwInvalidPattern e mkPatternVar fvarId e else if e.isMVar then mkLocalDeclFor e else let newE ← whnf e if newE != e then main newE else matchConstCtor e.getAppFn (fun _ => throwInvalidPattern e) fun v us => do let args := e.getAppArgs unless args.size == v.nparams + v.nfields do throwInvalidPattern e let params := args.extract 0 v.nparams let fields := args.extract v.nparams args.size let fields ← fields.mapM main pure $ Pattern.ctor v.name us params.toList fields.toList end ToDepElimPattern def withDepElimPatterns {α} (localDecls : Array LocalDecl) (ps : Array Expr) (k : Array LocalDecl → Array Pattern → TermElabM α) : TermElabM α := do let (patterns, s) ← (ps.mapM ToDepElimPattern.main).run { localDecls := localDecls } let localDecls ← s.localDecls.mapM fun d => instantiateLocalDeclMVars d /- toDepElimPatterns may have added new localDecls. Thus, we must update the local context before we execute `k` -/ let lctx ← getLCtx let lctx := localDecls.foldl (fun (lctx : LocalContext) d => lctx.erase d.fvarId) lctx let lctx := localDecls.foldl (fun (lctx : LocalContext) d => lctx.addDecl d) lctx withTheReader Meta.Context (fun ctx => { ctx with lctx := lctx }) $ k localDecls patterns private def withElaboratedLHS {α} (ref : Syntax) (patternVarDecls : Array PatternVarDecl) (patternStxs : Array Syntax) (matchType : Expr) (k : AltLHS → Expr → TermElabM α) : TermElabM α := do let (patterns, matchType) ← withSynthesize $ elabPatterns patternStxs matchType let localDecls ← finalizePatternDecls patternVarDecls let patterns ← patterns.mapM (instantiateMVars ·) withDepElimPatterns localDecls patterns fun localDecls patterns => k { ref := ref, fvarDecls := localDecls.toList, patterns := patterns.toList } matchType def elabMatchAltView (alt : MatchAltView) (matchType : Expr) : TermElabM (AltLHS × Expr) := withRef alt.ref do let (patternVars, alt) ← collectPatternVars alt trace[Elab.match]! "patternVars: {patternVars}" withPatternVars patternVars fun patternVarDecls => do withElaboratedLHS alt.ref patternVarDecls alt.patterns matchType fun altLHS matchType => do let rhs ← elabTermEnsuringType alt.rhs matchType let xs := altLHS.fvarDecls.toArray.map LocalDecl.toExpr let rhs ← if xs.isEmpty then pure $ mkSimpleThunk rhs else mkLambdaFVars xs rhs trace[Elab.match]! "rhs: {rhs}" pure (altLHS, rhs) def mkMatcher (elimName : Name) (matchType : Expr) (numDiscrs : Nat) (lhss : List AltLHS) : TermElabM MatcherResult := liftMetaM $ Meta.Match.mkMatcher elimName matchType numDiscrs lhss builtin_initialize registerOption `match.ignoreUnusedAlts { defValue := false, group := "", descr := "if true, do not generate error if an alternative is not used" } def ignoreUnusedAlts (opts : Options) : Bool := opts.get `match.ignoreUnusedAlts false def reportMatcherResultErrors (altLHSS : List AltLHS) (result : MatcherResult) : TermElabM Unit := do unless result.counterExamples.isEmpty do throwError! "missing cases:\n{Meta.Match.counterExamplesToMessageData result.counterExamples}" unless ignoreUnusedAlts (← getOptions) || result.unusedAltIdxs.isEmpty do let mut i := 0 for alt in altLHSS do if result.unusedAltIdxs.contains i then withRef alt.ref do logError "redundant alternative" i := i + 1 private def elabMatchAux (discrStxs : Array Syntax) (altViews : Array MatchAltView) (matchOptType : Syntax) (expectedType : Expr) : TermElabM Expr := do let (discrs, matchType, altViews) ← elabMatchTypeAndDiscrs discrStxs matchOptType altViews expectedType let matchAlts ← liftMacroM $ expandMacrosInPatterns altViews trace[Elab.match]! "matchType: {matchType}" let alts ← matchAlts.mapM $ fun alt => elabMatchAltView alt matchType /- We should not use `synthesizeSyntheticMVarsNoPostponing` here. Otherwise, we will not be able to elaborate examples such as: ``` def f (x : Nat) : Option Nat := none def g (xs : List (Nat × Nat)) : IO Unit := xs.forM fun x => match f x.fst with | _ => pure () ``` If `synthesizeSyntheticMVarsNoPostponing`, the example above fails at `x.fst` because the type of `x` is only available after we proces the last argument of `List.forM`. We apply pending default types to make sure we can process examples such as ``` let (a, b) := (0, 0) ``` -/ synthesizeSyntheticMVarsUsingDefault let rhss := alts.map Prod.snd let matchType ← instantiateMVars matchType let altLHSS ← alts.toList.mapM fun alt => do let altLHS ← Match.instantiateAltLHSMVars alt.1 withRef altLHS.ref do for d in altLHS.fvarDecls do if d.hasExprMVar then withExistingLocalDecls altLHS.fvarDecls do throwMVarError m!"invalid match-expression, type of pattern variable '{d.toExpr}' contains metavariables{indentExpr d.type}" for p in altLHS.patterns do if p.hasExprMVar then withExistingLocalDecls altLHS.fvarDecls do throwMVarError m!"invalid match-expression, pattern contains metavariables{indentExpr (← p.toExpr)}" pure altLHS let numDiscrs := discrs.size let matcherName ← mkAuxName `match let matcherResult ← mkMatcher matcherName matchType numDiscrs altLHSS let motive ← forallBoundedTelescope matchType numDiscrs fun xs matchType => mkLambdaFVars xs matchType reportMatcherResultErrors altLHSS matcherResult let r := mkApp matcherResult.matcher motive let r := mkAppN r discrs let r := mkAppN r rhss trace[Elab.match]! "result: {r}" pure r private def getDiscrs (matchStx : Syntax) : Array Syntax := matchStx[1].getSepArgs private def getMatchOptType (matchStx : Syntax) : Syntax := matchStx[2] private def expandNonAtomicDiscrs? (matchStx : Syntax) : TermElabM (Option Syntax) := let matchOptType := getMatchOptType matchStx; if matchOptType.isNone then do let discrs := getDiscrs matchStx; let allLocal ← discrs.allM fun discr => Option.isSome <$> isLocalIdent? discr[1] if allLocal then pure none else let rec loop (discrs : List Syntax) (discrsNew : Array Syntax) := do match discrs with | [] => let discrs := Syntax.mkSep discrsNew (mkAtomFrom matchStx ", "); pure (matchStx.setArg 1 discrs) | discr :: discrs => -- Recall that -- matchDiscr := parser! optional (ident >> ":") >> termParser let term := discr[1] match (← isLocalIdent? term) with | some _ => loop discrs (discrsNew.push discr) | none => withFreshMacroScope do let d ← `(_discr); unless isAuxDiscrName d.getId do -- Use assertion? throwError "unexpected internal auxiliary discriminant name" let discrNew := discr.setArg 1 d; let r ← loop discrs (discrsNew.push discrNew) `(let _discr := $term; $r) pure (some (← loop discrs.toList #[])) else -- We do not pull non atomic discriminants when match type is provided explicitly by the user pure none private def waitExpectedType (expectedType? : Option Expr) : TermElabM Expr := do tryPostponeIfNoneOrMVar expectedType? match expectedType? with | some expectedType => pure expectedType | none => mkFreshTypeMVar private def tryPostponeIfDiscrTypeIsMVar (matchStx : Syntax) : TermElabM Unit := do -- We don't wait for the discriminants types when match type is provided by user if getMatchOptType matchStx |>.isNone then let discrs := getDiscrs matchStx for discr in discrs do let term := discr[1] match (← isLocalIdent? term) with | none => throwErrorAt discr "unexpected discriminant" -- see `expandNonAtomicDiscrs? | some d => let dType ← inferType d trace[Elab.match]! "discr {d} : {dType}" tryPostponeIfMVar dType /- We (try to) elaborate a `match` only when the expected type is available. If the `matchType` has not been provided by the user, we also try to postpone elaboration if the type of a discriminant is not available. That is, it is of the form `(?m ...)`. We use `expandNonAtomicDiscrs?` to make sure all discriminants are local variables. This is a standard trick we use in the elaborator, and it is also used to elaborate structure instances. Suppose, we are trying to elaborate ``` match g x with | ... => ... ``` `expandNonAtomicDiscrs?` converts it intro ``` let _discr := g x match _discr with | ... => ... ``` Thus, at `tryPostponeIfDiscrTypeIsMVar` we only need to check whether the type of `_discr` is not of the form `(?m ...)`. Note that, the auxiliary variable `_discr` is expanded at `elabAtomicDiscr`. This elaboration technique is needed to elaborate terms such as: ```lean xs.filter fun (a, b) => a > b ``` which are syntax sugar for ```lean List.filter (fun p => match p with | (a, b) => a > b) xs ``` When we visit `match p with | (a, b) => a > b`, we don't know the type of `p` yet. -/ private def waitExpectedTypeAndDiscrs (matchStx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do tryPostponeIfNoneOrMVar expectedType? tryPostponeIfDiscrTypeIsMVar matchStx match expectedType? with | some expectedType => pure expectedType | none => mkFreshTypeMVar /- ``` parser!:leadPrec "match " >> sepBy1 matchDiscr ", " >> optType >> " with " >> matchAlts ``` Remark the `optIdent` must be `none` at `matchDiscr`. They are expanded by `expandMatchDiscr?`. -/ private def elabMatchCore (stx : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do let expectedType ← waitExpectedTypeAndDiscrs stx expectedType? let discrStxs := (getDiscrs stx).map fun d => d let altViews := getMatchAlts stx let matchOptType := getMatchOptType stx elabMatchAux discrStxs altViews matchOptType expectedType -- parser! "match " >> sepBy1 termParser ", " >> optType >> " with " >> matchAlts @[builtinTermElab «match»] def elabMatch : TermElab := fun stx expectedType? => match stx with | `(match $discr:term with | $y:ident => $rhs:term) => expandSimpleMatch stx discr y rhs expectedType? | `(match $discr:term : $type with | $y:ident => $rhs:term) => expandSimpleMatchWithType stx discr y type rhs expectedType? | _ => do match (← expandNonAtomicDiscrs? stx) with | some stxNew => withMacroExpansion stx stxNew $ elabTerm stxNew expectedType? | none => let discrs := getDiscrs stx; let matchOptType := getMatchOptType stx; if !matchOptType.isNone && discrs.any fun d => !d[0].isNone then throwErrorAt matchOptType "match expected type should not be provided when discriminants with equality proofs are used" elabMatchCore stx expectedType? @[builtinInit] private def regTraceClasses : IO Unit := do registerTraceClass `Elab.match; pure () -- parser!:leadPrec "nomatch " >> termParser @[builtinTermElab «nomatch»] def elabNoMatch : TermElab := fun stx expectedType? => match stx with | `(nomatch $discrExpr) => do let expectedType ← waitExpectedType expectedType? let discr := Syntax.node `Lean.Parser.Term.matchDiscr #[mkNullNode, discrExpr] elabMatchAux #[discr] #[] mkNullNode expectedType | _ => throwUnsupportedSyntax end Term end Elab end Lean