feat: improve collectPatternVars
Add support for `@` and named parameters. Fix how ctor fields are processed.
This commit is contained in:
parent
4cb3d5faa4
commit
276e6a55ba
2 changed files with 168 additions and 64 deletions
|
|
@ -184,13 +184,6 @@ forallBoundedTelescope ctorVal.type ctorVal.nparams fun ps _ =>
|
|||
private def throwAmbiguous {α} (fs : List Expr) : M α :=
|
||||
throwError ("ambiguous pattern, use fully qualified name, possible interpretations " ++ fs)
|
||||
|
||||
private def processVar (id : Name) (mustBeCtor : Bool := false) : M Unit := do
|
||||
when mustBeCtor $ throwCtorExpected;
|
||||
unless id.eraseMacroScopes.isAtomic $ throwError "invalid pattern variable, must be atomic";
|
||||
s ← get;
|
||||
when (s.found.contains id) $ 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 }
|
||||
|
||||
def resolveId? (stx : Syntax) : M (Option Expr) :=
|
||||
match stx with
|
||||
| Syntax.ident _ _ val preresolved => do
|
||||
|
|
@ -203,48 +196,164 @@ match stx with
|
|||
| _ => throwAmbiguous fs
|
||||
| _ => throwError "identifier expected"
|
||||
|
||||
/- Check whether `stx` is a pattern variable or constructor-like (i.e., constructor or constant tagged with `[matchPattern]` attribute)
|
||||
If `mustBeCtor == true`, then `stx` cannot be a pattern variable.
|
||||
private def throwInvalidPattern {α} : M α :=
|
||||
throwError "invalid pattern"
|
||||
|
||||
If `stx` is a constructor, then return the number of explicit arguments that are inductive type parameters. -/
|
||||
private def processIdAux (stx : Syntax) (mustBeCtor : Bool) : M Nat :=
|
||||
withRef stx do
|
||||
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 :=
|
||||
(funId : Syntax)
|
||||
(ctorVal? : Option ConstructorVal) -- It is `some`, if constructor application
|
||||
(explicit : Bool)
|
||||
(paramDecls : Array LocalDecl)
|
||||
(paramDeclIdx : Nat := 0)
|
||||
(namedArgs : Array NamedArg)
|
||||
(args : List Arg)
|
||||
(newArgs : Array Syntax := #[])
|
||||
|
||||
instance Context.inhabited : Inhabited Context :=
|
||||
⟨⟨arbitrary _, none, true, #[], 0, #[], [], #[]⟩⟩
|
||||
|
||||
private def isDone (ctx : Context) : Bool :=
|
||||
ctx.paramDeclIdx ≥ ctx.paramDecls.size
|
||||
|
||||
private def finalize (ctx : Context) : M Syntax :=
|
||||
if ctx.namedArgs.isEmpty && ctx.args.isEmpty then do
|
||||
fStx ← `(@$(ctx.funId):ident);
|
||||
pure $ mkAppStx 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.get! 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
|
||||
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
|
||||
| [] =>
|
||||
-- TODO: add support for `..`
|
||||
throwError ("explicit parameter is missing, unused named arguments " ++ toString (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
|
||||
hole ← `(_);
|
||||
pushNewArg collect accessible ctx (Arg.stx hole)
|
||||
|
||||
private partial def processCtorAppAux (collect : Syntax → M Syntax) : Context → M Syntax
|
||||
| ctx =>
|
||||
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 => do
|
||||
let arg := ctx.namedArgs.get! idx;
|
||||
let ctx := { ctx with namedArgs := ctx.namedArgs.eraseIdx idx };
|
||||
ctx ← pushNewArg collect accessible ctx arg.val;
|
||||
processCtorAppAux ctx
|
||||
| none => do
|
||||
ctx ← match d.binderInfo with
|
||||
| BinderInfo.implicit => processImplicitArg collect accessible ctx
|
||||
| BinderInfo.instImplicit => processImplicitArg collect accessible ctx
|
||||
| _ => processExplicitArg collect accessible ctx;
|
||||
processCtorAppAux ctx
|
||||
|
||||
def processCtorApp (collect : Syntax → M Syntax) (f : Syntax) (namedArgs : Array NamedArg) (args : Array Arg) : M Syntax := do
|
||||
let args := args.toList;
|
||||
(fId, explicit) ← match_syntax f with
|
||||
| `($fId:ident) => pure (fId, false)
|
||||
| `(@$fId:ident) => pure (fId, true)
|
||||
| _ => throwError "identifier expected";
|
||||
some (Expr.const fName _ _) ← resolveId? fId | throwCtorExpected;
|
||||
fInfo ← getConstInfo fName;
|
||||
forallTelescopeReducing fInfo.type fun xs _ => do
|
||||
paramDecls ← xs.mapM getFVarLocalDecl;
|
||||
match fInfo with
|
||||
| ConstantInfo.ctorInfo val =>
|
||||
processCtorAppAux collect { funId := fId, explicit := explicit, ctorVal? := val, paramDecls := paramDecls, namedArgs := namedArgs, args := args }
|
||||
| _ => do
|
||||
env ← getEnv;
|
||||
if hasMatchPatternAttribute env fName then
|
||||
processCtorAppAux collect { funId := fId, explicit := explicit, ctorVal? := none, paramDecls := paramDecls, namedArgs := namedArgs, args := args }
|
||||
else
|
||||
throwCtorExpected
|
||||
|
||||
end CtorApp
|
||||
|
||||
def processCtorApp (collect : Syntax → M Syntax) (stx : Syntax) : M Syntax := do
|
||||
(f, namedArgs, args) ← liftM $ expandApp stx;
|
||||
CtorApp.processCtorApp collect f namedArgs args
|
||||
|
||||
def processCtor (collect : Syntax → M Syntax) (stx : Syntax) : M Syntax := do
|
||||
CtorApp.processCtorApp collect stx #[] #[]
|
||||
|
||||
private def processVar (idStx : Syntax) : M Syntax := do
|
||||
unless idStx.isIdent $
|
||||
throwErrorAt idStx "identifier expected";
|
||||
let id := idStx.getId;
|
||||
unless id.eraseMacroScopes.isAtomic $ throwError "invalid pattern variable, must be atomic";
|
||||
s ← get;
|
||||
when (s.found.contains id) $ 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
|
||||
env ← getEnv;
|
||||
f? ← resolveId? stx;
|
||||
match f? with
|
||||
| none => do processVar stx.getId mustBeCtor; pure 0
|
||||
| none => processVar stx
|
||||
| some f => match f with
|
||||
| Expr.const fName _ _ => do
|
||||
match env.find? fName with
|
||||
| some $ ConstantInfo.ctorInfo val => liftM $ getNumExplicitCtorParams val
|
||||
| some $ info =>
|
||||
if hasMatchPatternAttribute env fName then pure 0
|
||||
else do processVar stx.getId mustBeCtor; pure 0
|
||||
| none => throwCtorExpected
|
||||
| _ => do processVar stx.getId mustBeCtor; pure 0
|
||||
|
||||
private def processCtor (stx : Syntax) : M Nat :=
|
||||
processIdAux stx true
|
||||
|
||||
private def processId (stx : Syntax) : M Unit := do
|
||||
_ ← processIdAux stx false; pure ()
|
||||
|
||||
private def throwInvalidPattern {α} : M α :=
|
||||
throwError "invalid pattern"
|
||||
| some (ConstantInfo.ctorInfo _) => processCtor collect stx
|
||||
| some _ =>
|
||||
if hasMatchPatternAttribute env fName then
|
||||
processCtor collect stx
|
||||
else
|
||||
processVar stx
|
||||
| none => throwCtorExpected
|
||||
| _ => processVar stx
|
||||
|
||||
private partial def collect : Syntax → M Syntax
|
||||
| stx@(Syntax.node k args) => withRef stx $ withFreshMacroScope $
|
||||
if k == `Lean.Parser.Term.app then do
|
||||
let appFn := args.get! 0;
|
||||
let appArgs := (args.get! 1).getArgs;
|
||||
appArgs.forM fun appArg =>
|
||||
when (appArg.isOfKind `Lean.Parser.Term.namedPattern) $
|
||||
throwErrorAt appArg "named parameters are not allowed in patterns";
|
||||
/- We must skip explict inducitve datatype parameters since they are by default inaccessible.
|
||||
Example: `A` is inaccessible term at `Sum.inl A b` -/
|
||||
numArgsToSkip ← processCtor appFn;
|
||||
appArgs ← appArgs.mapIdxM fun i arg => if i < numArgsToSkip then pure arg else collect arg;
|
||||
pure $ Syntax.node k $ args.set! 1 (mkNullNode appArgs)
|
||||
processCtorApp collect stx
|
||||
else if k == `Lean.Parser.Term.anonymousCtor then do
|
||||
elems ← (args.get! 1).getArgs.mapSepElemsM $ collect;
|
||||
pure $ Syntax.node k $ args.set! 1 $ mkNullNode elems
|
||||
|
|
@ -288,13 +397,12 @@ private partial def collect : Syntax → M Syntax
|
|||
let arg := arg.setArg 1 s;
|
||||
pure $ Syntax.node k $ args.set! 1 arg
|
||||
else if k == `Lean.Parser.Term.explicitUniv then do
|
||||
_ ← processCtor (stx.getArg 0);
|
||||
pure stx
|
||||
processCtor collect (stx.getArg 0)
|
||||
else if k == `Lean.Parser.Term.namedPattern then do
|
||||
/- Recall that
|
||||
def namedPattern := check... >> tparser! "@" >> termParser -/
|
||||
let id := stx.getArg 0;
|
||||
processVar id.getId;
|
||||
processVar id;
|
||||
let pat := stx.getArg 2;
|
||||
pat ← collect pat;
|
||||
`(namedPattern $id $pat)
|
||||
|
|
@ -310,9 +418,8 @@ private partial def collect : Syntax → M Syntax
|
|||
throwError "invalid pattern, notation is ambiguous"
|
||||
else
|
||||
throwInvalidPattern
|
||||
| stx@(Syntax.ident _ _ _ _) => do
|
||||
processId stx;
|
||||
pure stx
|
||||
| stx@(Syntax.ident _ _ _ _) =>
|
||||
processId collect stx
|
||||
| stx =>
|
||||
throwInvalidPattern
|
||||
|
||||
|
|
@ -420,14 +527,6 @@ modify $ fun s => { s with found := s.found.insert fvarId }
|
|||
private def throwInvalidPattern {α} (e : Expr) : M α :=
|
||||
throwError ("invalid pattern " ++ indentExpr e)
|
||||
|
||||
private def getFieldsBinderInfoAux (ctorVal : ConstructorVal) : Nat → Expr → Array BinderInfo → Array BinderInfo
|
||||
| i, Expr.forallE _ d b c, bis =>
|
||||
if i < ctorVal.nparams then
|
||||
getFieldsBinderInfoAux (i+1) b bis
|
||||
else
|
||||
getFieldsBinderInfoAux (i+1) b (bis.push c.binderInfo)
|
||||
| _, _, bis => bis
|
||||
|
||||
/- 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!;
|
||||
|
|
@ -452,9 +551,6 @@ match val? with
|
|||
| some i => s.localDecls.insertAt i newDecl };
|
||||
pure $ Pattern.var fvarId
|
||||
|
||||
private def getFieldsBinderInfo (ctorVal : ConstructorVal) : Array BinderInfo :=
|
||||
getFieldsBinderInfoAux ctorVal 0 ctorVal.type #[]
|
||||
|
||||
partial def main : Expr → M Pattern
|
||||
| e =>
|
||||
let isLocalDecl (fvarId : FVarId) : M Bool := do {
|
||||
|
|
@ -505,14 +601,7 @@ partial def main : Expr → M Pattern
|
|||
unless (args.size == v.nparams + v.nfields) $ throwInvalidPattern e;
|
||||
let params := args.extract 0 v.nparams;
|
||||
let fields := args.extract v.nparams args.size;
|
||||
let binderInfos := getFieldsBinderInfo v;
|
||||
fields ← fields.mapIdxM fun i field => do {
|
||||
let binderInfo := binderInfos.get! i;
|
||||
if binderInfo.isExplicit then
|
||||
main field
|
||||
else
|
||||
mkInaccessible field
|
||||
};
|
||||
fields ← fields.mapM main;
|
||||
pure $ Pattern.ctor v.name us params.toList fields.toList
|
||||
|
||||
end ToDepElimPattern
|
||||
|
|
@ -544,7 +633,6 @@ withPatternVars patternVars fun patternVarDecls => do
|
|||
let xs := altLHS.fvarDecls.toArray.map LocalDecl.toExpr;
|
||||
rhs ← if xs.isEmpty then pure $ mkThunk rhs else mkLambdaFVars xs rhs;
|
||||
trace `Elab.match fun _ => "rhs: " ++ rhs;
|
||||
-- TODO: we should promote `.(?m ...)` to pattern variables too. This can happen when users misuse `{}` in constructors
|
||||
-- TODO: check whether altLHS still has metavariables
|
||||
pure (altLHS, rhs)
|
||||
|
||||
|
|
|
|||
|
|
@ -58,3 +58,19 @@ aux xs
|
|||
|
||||
theorem ex6 : head3 [1, 2] = some 1 :=
|
||||
rfl
|
||||
|
||||
inductive Vec.{u} (α : Type u) : Nat → Type u
|
||||
| nil : Vec α 0
|
||||
| cons {n} (head : α) (tail : Vec α n) : Vec α (n+1)
|
||||
|
||||
def Vec.mapHead1 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
|
||||
| _, nil, nil, f => none
|
||||
| _, cons a as, cons b bs, f => some (f a b)
|
||||
|
||||
def Vec.mapHead2 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
|
||||
| _, nil, nil, f => none
|
||||
| _, @cons _ n a as, cons b bs, f => some (f a b)
|
||||
|
||||
def Vec.mapHead3 {α β δ} : {n : Nat} → Vec α n → Vec β n → (α → β → δ) → Option δ
|
||||
| _, nil, nil, f => none
|
||||
| _, cons (tail := as) (head := a), cons b bs, f => some (f a b)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue