lean4-htt/src/Lean/Structure.lean
Leonardo de Moura 304c80d610 feat: use <|
2020-11-19 09:03:38 -08:00

152 lines
6.6 KiB
Text

/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
Helper functions for retrieving structure information.
-/
import Lean.Environment
import Lean.ProjFns
/- TODO: We currently assume that the projection function for field `fieldName` at structure `structName` is
`structName ++ fieldName`. This is incorrect for private projections.
We will fix this by storing a mapping from `structure` + `fieldName` to projection function name in the environment.
This modification will impact functions such as `getStructureFields` and `getProjFnForField?` -/
namespace Lean
/--
Return true iff `constName` is the a non-recursive inductive
datatype that has only one constructor. -/
def isStructureLike (env : Environment) (constName : Name) : Bool :=
match env.find? constName with
| some (ConstantInfo.inductInfo { isRec := false, ctors := [ctor], .. }) => true
| _ => false
/-- We mark subobject fields by prefixing them with "_" in the structure's intro rule. -/
def mkInternalSubobjectFieldName (fieldName : Name) : Name :=
fieldName.appendBefore "_"
def isInternalSubobjectFieldName : Name → Bool
| Name.str _ s _ => s.length > 0 && s.get 0 == '_'
| _ => false
def deinternalizeFieldName : Name → Name
| n@(Name.str p s _) => if s.length > 0 && s.get 0 == '_' then Name.mkStr p (s.drop 1) else n
| n => n
def getStructureCtor (env : Environment) (constName : Name) : ConstructorVal :=
match env.find? constName with
| some (ConstantInfo.inductInfo { nparams := nparams, isRec := false, ctors := [ctorName], .. }) =>
match env.find? ctorName with
| some (ConstantInfo.ctorInfo val) => val
| _ => panic! "ill-formed environment"
| _ => panic! "structure expected"
private def getStructureFieldsAux (nparams : Nat) : Nat → Expr → Array Name → Array Name
| i, Expr.forallE n d b _, fieldNames =>
if i < nparams then
getStructureFieldsAux nparams (i+1) b fieldNames
else
getStructureFieldsAux nparams (i+1) b <| fieldNames.push <| deinternalizeFieldName n
| _, _, fieldNames => fieldNames
-- TODO: fix. See comment in the beginning of the file
def getStructureFields (env : Environment) (structName : Name) : Array Name :=
let ctor := getStructureCtor env structName;
getStructureFieldsAux ctor.nparams 0 ctor.type #[]
private def isSubobjectFieldAux (nparams : Nat) (target : Name) : Nat → Expr → Option Name
| i, Expr.forallE n d b _ =>
if i < nparams then
isSubobjectFieldAux nparams target (i+1) b
else if n == target then
match d.getAppFn with
| Expr.const parentStructName _ _ => some parentStructName
| _ => panic! "ill-formed structure"
else
isSubobjectFieldAux nparams target (i+1) b
| _, _ => none
-- TODO: fix. See comment in the beginning of the file
/-- If `fieldName` represents the relation to a parent structure `S`, return `S` -/
def isSubobjectField? (env : Environment) (structName : Name) (fieldName : Name) : Option Name :=
let ctor := getStructureCtor env structName;
isSubobjectFieldAux ctor.nparams (mkInternalSubobjectFieldName fieldName) 0 ctor.type
/-- Return immediate parent structures -/
def getParentStructures (env : Environment) (structName : Name) : Array Name :=
let fieldNames := getStructureFields env structName;
fieldNames.foldl (init := #[]) fun acc fieldName =>
match isSubobjectField? env structName fieldName with
| some parentStructName => acc.push parentStructName
| none => acc
/-- `findField? env S fname`. If `fname` is defined in a parent `S'` of `S`, return `S'` -/
partial def findField? (env : Environment) (structName : Name) (fieldName : Name) : Option Name :=
if (getStructureFields env structName).contains fieldName then
some structName
else
getParentStructures env structName |>.findSome? fun parentStructName => findField? env parentStructName fieldName
private partial def getStructureFieldsFlattenedAux (env : Environment) (structName : Name) (fullNames : Array Name) : Array Name :=
(getStructureFields env structName).foldl (init := fullNames) fun fullNames fieldName =>
let fullNames := fullNames.push fieldName;
match isSubobjectField? env structName fieldName with
| some parentStructName => getStructureFieldsFlattenedAux env parentStructName fullNames
| none => fullNames
def getStructureFieldsFlattened (env : Environment) (structName : Name) : Array Name :=
getStructureFieldsFlattenedAux env structName #[]
-- TODO: fix. See comment in the beginning of the file
private def hasProjFn (env : Environment) (structName : Name) (nparams : Nat) : Nat → Expr → Bool
| i, Expr.forallE n d b _ =>
if i < nparams then
hasProjFn env structName nparams (i+1) b
else
let fullFieldName := structName ++ deinternalizeFieldName n;
env.isProjectionFn fullFieldName
| _, _ => false
/--
Return true if `constName` is the name of an inductive datatype
created using the `structure` or `class` commands.
We perform the check by testing whether auxiliary projection functions
have been created. -/
def isStructure (env : Environment) (constName : Name) : Bool :=
if isStructureLike env constName then
let ctor := getStructureCtor env constName;
hasProjFn env constName ctor.nparams 0 ctor.type
else
false
-- TODO: fix. See comment in the beginning of the file
def getProjFnForField? (env : Environment) (structName : Name) (fieldName : Name) : Option Name :=
let fieldNames := getStructureFields env structName;
if fieldNames.any fun n => fieldName == n then
some (structName ++ fieldName)
else
none
partial def getPathToBaseStructureAux (env : Environment) (baseStructName : Name) (structName : Name) (path : List Name) : Option (List Name) :=
if baseStructName == structName then
some path.reverse
else
let fieldNames := getStructureFields env structName;
fieldNames.findSome? fun fieldName =>
match isSubobjectField? env structName fieldName with
| none => none
| some parentStructName =>
match getProjFnForField? env structName fieldName with
| none => none
| some projFn => getPathToBaseStructureAux env baseStructName parentStructName (projFn :: path)
/--
If `baseStructName` is an ancestor structure for `structName`, then return a sequence of projection functions
to go from `structName` to `baseStructName`. -/
def getPathToBaseStructure? (env : Environment) (baseStructName : Name) (structName : Name) : Option (List Name) :=
getPathToBaseStructureAux env baseStructName structName []
end Lean