/- Copyright (c) 2020 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura, Sebastian Ullrich -/ import Lean.Util.CollectLevelParams import Lean.Elab.DeclUtil import Lean.Elab.Definition import Lean.Elab.Inductive import Lean.Elab.Structure import Lean.Elab.MutualDef namespace Lean namespace Elab namespace Command open Meta /- Auxiliary function for `expandDeclNamespace?` -/ def expandDeclIdNamespace? (declId : Syntax) : Option (Name × Syntax) := let (id, optUnivDeclStx) := expandDeclIdCore declId; let scpView := extractMacroScopes id; match scpView.name with | Name.str Name.anonymous s _ => none | Name.str pre s _ => let nameNew := { scpView with name := mkNameSimple s }.review; if declId.isIdent then some (pre, mkIdentFrom declId nameNew) else some (pre, declId.setArg 0 (mkIdentFrom declId nameNew)) | _ => none /- given declarations such as `@[...] def Foo.Bla.f ...` return `some (Foo.Bla, @[...] def f ...)` -/ def expandDeclNamespace? (stx : Syntax) : Option (Name × Syntax) := if !stx.isOfKind `Lean.Parser.Command.declaration then none else let decl := stx.getArg 1; let k := decl.getKind; if k == `Lean.Parser.Command.abbrev || k == `Lean.Parser.Command.def || k == `Lean.Parser.Command.theorem || k == `Lean.Parser.Command.constant || k == `Lean.Parser.Command.axiom || k == `Lean.Parser.Command.inductive || k == `Lean.Parser.Command.structure then match expandDeclIdNamespace? (decl.getArg 1) with | some (ns, declId) => some (ns, stx.setArg 1 (decl.setArg 1 declId)) | none => none else if k == `Lean.Parser.Command.instance then let optDeclId := decl.getArg 1; if optDeclId.isNone then none else match expandDeclIdNamespace? (optDeclId.getArg 0) with | some (ns, declId) => some (ns, stx.setArg 1 (decl.setArg 1 (optDeclId.setArg 0 declId))) | none => none else if k == `Lean.Parser.Command.classInductive then match expandDeclIdNamespace? (decl.getArg 2) with | some (ns, declId) => some (ns, stx.setArg 1 (decl.setArg 2 declId)) | none => none else none def elabAxiom (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do -- parser! "axiom " >> declId >> declSig let declId := stx.getArg 1; let (binders, typeStx) := expandDeclSig (stx.getArg 2); scopeLevelNames ← getLevelNames; ⟨name, declName, allUserLevelNames⟩ ← expandDeclId declId modifiers; runTermElabM declName $ fun vars => Term.withLevelNames allUserLevelNames $ Term.elabBinders binders.getArgs fun xs => do applyAttributes declName modifiers.attrs AttributeApplicationTime.beforeElaboration; type ← Term.elabType typeStx; Term.synthesizeSyntheticMVarsNoPostponing; type ← instantiateMVars type; type ← mkForallFVars xs type; (type, _) ← mkForallUsedOnly vars type; (type, _) ← Term.levelMVarToParam type; let usedParams := (collectLevelParams {} type).params; match sortDeclLevelParams scopeLevelNames allUserLevelNames usedParams with | Except.error msg => throwErrorAt stx msg | Except.ok levelParams => do let decl := Declaration.axiomDecl { name := declName, lparams := levelParams, type := type, isUnsafe := modifiers.isUnsafe }; Term.ensureNoUnassignedMVars decl; addDecl decl; applyAttributes declName modifiers.attrs AttributeApplicationTime.afterTypeChecking; applyAttributes declName modifiers.attrs AttributeApplicationTime.afterCompilation /- parser! "inductive " >> declId >> optDeclSig >> many ctor parser! try ("class " >> "inductive ") >> declId >> optDeclSig >> many ctor Remark: numTokens == 1 for regular `inductive` and 2 for `class inductive`. -/ private def inductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) (numTokens := 1) : CommandElabM InductiveView := do checkValidInductiveModifier modifiers; let (binders, type?) := expandOptDeclSig (decl.getArg (numTokens + 1)); let declId := decl.getArg numTokens; ⟨name, declName, levelNames⟩ ← expandDeclId declId modifiers; ctors ← (decl.getArg (numTokens + 2)).getArgs.mapM fun ctor => withRef ctor do { -- def ctor := parser! " | " >> declModifiers >> ident >> optional inferMod >> optDeclSig ctorModifiers ← elabModifiers (ctor.getArg 1); when (ctorModifiers.isPrivate && modifiers.isPrivate) $ throwError "invalid 'private' constructor in a 'private' inductive datatype"; when (ctorModifiers.isProtected && modifiers.isPrivate) $ throwError "invalid 'protected' constructor in a 'private' inductive datatype"; checkValidCtorModifier ctorModifiers; let ctorName := ctor.getIdAt 2; let ctorName := declName ++ ctorName; ctorName ← withRef (ctor.getArg 2) $ applyVisibility ctorModifiers.visibility ctorName; let inferMod := !(ctor.getArg 3).isNone; let (binders, type?) := expandOptDeclSig (ctor.getArg 4); pure { ref := ctor, modifiers := ctorModifiers, declName := ctorName, inferMod := inferMod, binders := binders, type? := type? : CtorView } }; pure { ref := decl, modifiers := modifiers, shortDeclName := name, declName := declName, levelNames := levelNames, binders := binders, type? := type?, ctors := ctors } private def classInductiveSyntaxToView (modifiers : Modifiers) (decl : Syntax) : CommandElabM InductiveView := inductiveSyntaxToView modifiers decl 2 def elabInductive (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do v ← inductiveSyntaxToView modifiers stx; elabInductiveViews #[v] def elabClassInductive (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit := do let modifiers := modifiers.addAttribute { name := `class }; v ← classInductiveSyntaxToView modifiers stx; elabInductiveViews #[v] @[builtinCommandElab declaration] def elabDeclaration : CommandElab := fun stx => match expandDeclNamespace? stx with | some (ns, newStx) => do let ns := mkIdentFrom stx ns; newStx ← `(namespace $ns:ident $newStx end $ns:ident); withMacroExpansion stx newStx $ elabCommand newStx | none => do modifiers ← elabModifiers (stx.getArg 0); let decl := stx.getArg 1; let declKind := decl.getKind; if declKind == `Lean.Parser.Command.axiom then elabAxiom modifiers decl else if declKind == `Lean.Parser.Command.inductive then elabInductive modifiers decl else if declKind == `Lean.Parser.Command.classInductive then elabClassInductive modifiers decl else if declKind == `Lean.Parser.Command.structure then elabStructure modifiers decl else if isDefLike decl then do view ← mkDefView modifiers decl; elabDefLike view else throwError "unexpected declaration" /- Return true if all elements of the mutual-block are inductive declarations. -/ private def isMutualInductive (stx : Syntax) : Bool := (stx.getArg 1).getArgs.all $ fun elem => let decl := elem.getArg 1; let declKind := decl.getKind; declKind == `Lean.Parser.Command.inductive private def elabMutualInductive (elems : Array Syntax) : CommandElabM Unit := do views ← elems.mapM $ fun stx => do { modifiers ← elabModifiers (stx.getArg 0); inductiveSyntaxToView modifiers (stx.getArg 1) }; elabInductiveViews views /- Return true if all elements of the mutual-block are definitions/theorems/abbrevs. -/ private def isMutualDef (stx : Syntax) : Bool := (stx.getArg 1).getArgs.all $ fun elem => let decl := elem.getArg 1; isDefLike decl private def isMutualPreambleCommand (stx : Syntax) : Bool := let k := stx.getKind; k == `Lean.Parser.Command.variable || k == `Lean.Parser.Command.variables || k == `Lean.Parser.Command.universe || k == `Lean.Parser.Command.universes || k == `Lean.Parser.Command.check || k == `Lean.Parser.Command.set_option || k == `Lean.Parser.Command.open private partial def splitMutualPreamble (elems : Array Syntax) : Nat → Option (Array Syntax × Array Syntax) | i => if h : i < elems.size then let elem := elems.get ⟨i, h⟩; if isMutualPreambleCommand elem then splitMutualPreamble (i+1) else if i == 0 then none -- `mutual` block does not contain any preamble commands else some (elems.extract 0 i, elems.extract i elems.size) else none -- a `mutual` block containing only preamble commands is not a valid `mutual` block private def expandMutualPreamble? (stx : Syntax) : MacroM (Option Syntax) := match splitMutualPreamble (stx.getArg 1).getArgs 0 with | some (preamble, rest) => do secCmd ← `(section); let newMutual := stx.setArg 1 (mkNullNode rest); endCmd ← `(end); pure (some (mkNullNode (#[secCmd] ++ preamble ++ #[newMutual] ++ #[endCmd]))) | none => pure none private def expandMutualElement? (stx : Syntax) : CommandElabM (Option Syntax) := do env ← getEnv; let elems := (stx.getArg 1).getArgs; (elemsNew, modified) ← elems.foldlM (fun (acc : Array Syntax × Bool) elem => do let (elemsNew, modified) := acc; elem? ← liftMacroM $ expandMacro? env elem; match elem? with | some elemNew => pure (elemsNew.push elemNew, true) | none => pure (elemsNew.push elem, modified)) (#[], false); if modified then pure (some (stx.setArg 1 (mkNullNode elemsNew))) else pure none private def expandMutualNamespace? (stx : Syntax) : MacroM (Option Syntax) := do let elems := (stx.getArg 1).getArgs; (ns?, elems) ← elems.foldlM (fun (acc : Option Name × Array Syntax) (elem : Syntax) => let (ns?, elems) := acc; match ns?, expandDeclNamespace? elem with | _, none => pure (ns?, elems.push elem) | none, some (ns, elem) => pure (some ns, elems.push elem) | some nsCurr, some (nsNew, elem) => if nsCurr == nsNew then pure (ns?, elems.push elem) else Macro.throwError elem ("conflicting namespaces in mutual declaration, using namespace '" ++ toString nsNew ++ "', but used '" ++ toString nsCurr ++ "' in previous declaration")) (none, #[]); match ns? with | none => pure none | some ns => let ns := mkIdentFrom stx ns; let stxNew := stx.setArg 1 (mkNullNode elems); `(namespace $ns:ident $stxNew end $ns:ident) @[builtinCommandElab «mutual»] def elabMutual : CommandElab := fun stx => do stxNew? ← liftMacroM $ expandMutualPreamble? stx; match stxNew? with | some stxNew => withMacroExpansion stx stxNew $ elabCommand stxNew | none => do stxNew? ← expandMutualElement? stx; match stxNew? with | some stxNew => withMacroExpansion stx stxNew $ elabCommand stxNew | none => do stxNew? ← liftMacroM $ expandMutualNamespace? stx; match stxNew? with | some stxNew => withMacroExpansion stx stxNew $ elabCommand stxNew | none => if isMutualInductive stx then elabMutualInductive (stx.getArg 1).getArgs else if isMutualDef stx then elabMutualDef (stx.getArg 1).getArgs else throwError "invalid mutual block" end Command end Elab end Lean