@@ -7,6 +7,7 @@ import Lean.Parser.Command
77import Lean.Meta.Closure
88import Lean.Meta.SizeOf
99import Lean.Meta.Injective
10+ import Lean.Meta.Structure
1011import Lean.Elab.Command
1112import Lean.Elab.DeclModifiers
1213import Lean.Elab.DeclUtil
@@ -227,23 +228,30 @@ private def validStructType (type : Expr) : Bool :=
227228 | Expr.sort .. => true
228229 | _ => false
229230
230- private def checkParentIsStructure (parent : Expr) : TermElabM Name :=
231- match parent.getAppFn with
232- | Expr.const c _ _ => do
233- unless isStructure (← getEnv) c do
234- throwError "'{c}' is not a structure"
235- pure c
236- | _ => throwError "expected structure"
237-
238231private def findFieldInfo? (infos : Array StructFieldInfo) (fieldName : Name) : Option StructFieldInfo :=
239232 infos.find? fun info => info.name == fieldName
240233
241234private def containsFieldName (infos : Array StructFieldInfo) (fieldName : Name) : Bool :=
242235 (findFieldInfo? infos fieldName).isSome
243236
237+ register_builtin_option structureDiamondWarning : Bool := {
238+ defValue := true -- TODO: set as false after finishing support for diamonds
239+ descr := "enable/disable warning messages for structure diamonds"
240+ }
241+
242+ /-- Return `some fieldName` if field `fieldName` of the parent structure `parentStructName` is already in `infos` -/
243+ private def findExistingField? (infos : Array StructFieldInfo) (parentStructName : Name) : CoreM (Option Name) := do
244+ let fieldNames := getStructureFieldsFlattened (← getEnv) parentStructName
245+ for fieldName in fieldNames do
246+ if containsFieldName infos fieldName then
247+ return some fieldName
248+ return none
249+
244250private partial def processSubfields (structDeclName : Name) (parentFVar : Expr) (parentStructName : Name) (subfieldNames : Array Name)
245251 (infos : Array StructFieldInfo) (k : Array StructFieldInfo → TermElabM α) : TermElabM α :=
246- let rec loop (i : Nat) (infos : Array StructFieldInfo) := do
252+ go 0 infos
253+ where
254+ go (i : Nat) (infos : Array StructFieldInfo) := do
247255 if h : i < subfieldNames.size then
248256 let subfieldName := subfieldNames.get ⟨i, h⟩
249257 if containsFieldName infos subfieldName then
@@ -255,28 +263,65 @@ private partial def processSubfields (structDeclName : Name) (parentFVar : Expr)
255263 its default value is overwritten in the structure. -/
256264 let declName := structDeclName ++ subfieldName
257265 let infos := infos.push { name := subfieldName, declName, fvar := subfieldFVar, kind := StructFieldKind.fromParent }
258- loop (i+1 ) infos
266+ go (i+1 ) infos
259267 else
260268 k infos
261- loop 0 infos
262-
263- private partial def withParents (view : StructView) (i : Nat) (infos : Array StructFieldInfo) (k : Array StructFieldInfo → TermElabM α) : TermElabM α := do
264- if h : i < view.parents.size then
265- let parentStx := view.parents.get ⟨i, h⟩
266- withRef parentStx do
267- let parent ← Term.elabType parentStx
268- let parentName ← checkParentIsStructure parent
269- let toParentName := Name.mkSimple $ "to" ++ parentName.eraseMacroScopes.getString! -- erase macro scopes?
270- if containsFieldName infos toParentName then
271- throwErrorAt parentStx "field '{toParentName}' has already been declared"
272- let env ← getEnv
273- let binfo := if view.isClass && isClass env parentName then BinderInfo.instImplicit else BinderInfo.default
274- withLocalDecl toParentName binfo parent fun parentFVar =>
275- let infos := infos.push { name := toParentName, declName := view.declName ++ toParentName, fvar := parentFVar, kind := StructFieldKind.subobject }
276- let subfieldNames := getStructureFieldsFlattened env parentName
277- processSubfields view.declName parentFVar parentName subfieldNames infos fun infos => withParents view (i+1 ) infos k
278- else
279- k infos
269+
270+ private partial def copyNewFieldsFrom (view : StructView) (infos : Array StructFieldInfo) (parent : Expr) (parentStructName : Name) (k : Array StructFieldInfo → TermElabM α) : TermElabM α := do
271+ let fieldNames := getStructureFieldsFlattened (← getEnv) parentStructName
272+ let rec go (i : Nat) (infos : Array StructFieldInfo) : TermElabM α := do
273+ if h : i < fieldNames.size then
274+ let fieldName := fieldNames.get ⟨i, h⟩
275+ let fieldType ← getFieldType parent fieldName
276+ match (← findFieldInfo? infos fieldName) with
277+ | some existingFieldInfo =>
278+ let existingFieldType ← inferType existingFieldInfo.fvar
279+ unless (← isDefEq fieldType existingFieldType) do
280+ throwError "parent field type mismatch, field '{fieldName}' from parent '{parentStructName}' {← mkHasTypeButIsExpectedMsg fieldType existingFieldType}"
281+ go (i+1 ) infos
282+ | none =>
283+ /- TODO: we are ignoring the following information from the `fieldName` declaraion at `parentStructName`.
284+ - Binder annotation
285+ - Visibility annotation (private/protected)
286+ - `inferMod`
287+ - Default value.
288+ -/
289+ withLocalDeclD fieldName fieldType fun fieldFVar => do
290+ let fieldDeclName := view.declName ++ fieldName
291+ let infos := infos.push { name := fieldName, declName := fieldDeclName, fvar := fieldFVar, value? := none,
292+ kind := StructFieldKind.newField, inferMod := false }
293+ go (i+1 ) infos
294+ else
295+ k infos
296+ go 0 infos
297+
298+ private partial def withParents (view : StructView) (k : Array StructFieldInfo → TermElabM α) : TermElabM α := do
299+ go 0 #[]
300+ where
301+ go (i : Nat) (infos : Array StructFieldInfo) : TermElabM α := do
302+ if h : i < view.parents.size then
303+ let parentStx := view.parents.get ⟨i, h⟩
304+ withRef parentStx do
305+ let parent ← Term.elabType parentStx
306+ let parentStructName ← getStructureName parent
307+ if let some existingFieldName ← findExistingField? infos parentStructName then
308+ if structureDiamondWarning.get (← getOptions) then
309+ logWarning s! "field '{ existingFieldName} ' from '{ parentStructName} ' has already been declared"
310+ copyNewFieldsFrom view infos parent parentStructName fun infos => go (i+1 ) infos
311+ -- TODO: if `class`, then we need to create a let-decl that stores the local instance for the `parentStructure`
312+ else
313+ let toParentName := Name.mkSimple $ "to" ++ parentStructName.eraseMacroScopes.getString! -- erase macro scopes?
314+ if containsFieldName infos toParentName then
315+ throwErrorAt parentStx "field '{toParentName}' has already been declared"
316+ let env ← getEnv
317+ let binfo := if view.isClass && isClass env parentStructName then BinderInfo.instImplicit else BinderInfo.default
318+ withLocalDecl toParentName binfo parent fun parentFVar =>
319+ let infos := infos.push { name := toParentName, declName := view.declName ++ toParentName, fvar := parentFVar, kind := StructFieldKind.subobject }
320+ let subfieldNames := getStructureFieldsFlattened env parentStructName
321+ processSubfields view.declName parentFVar parentStructName subfieldNames infos fun infos => go (i+1 ) infos
322+ else
323+ k infos
324+
280325
281326private def elabFieldTypeValue (view : StructFieldView) : TermElabM (Option Expr × Option Expr) := do
282327 Term.withAutoBoundImplicit <| Term.elabBinders view.binders.getArgs fun params => do
@@ -516,7 +561,7 @@ private def elabStructureView (view : StructView) : TermElabM Unit := do
516561 let type ← Term.elabType view.type
517562 unless validStructType type do throwErrorAt view.type "expected Type"
518563 withRef view.ref do
519- withParents view 0 #[] fun fieldInfos =>
564+ withParents view fun fieldInfos =>
520565 withFields view.fields 0 fieldInfos fun fieldInfos => do
521566 Term.synthesizeSyntheticMVarsNoPostponing
522567 let u ← getResultUniverse type
@@ -554,6 +599,7 @@ private def elabStructureView (view : StructView) : TermElabM Unit := do
554599 let projInstances := instParents.toList.map fun info => info.declName
555600 Term.applyAttributesAt view.declName view.modifiers.attrs AttributeApplicationTime.afterTypeChecking
556601 projInstances.forM fun declName => addInstance declName AttributeKind.global (eval_prio default)
602+ -- TODO: we must create `to` functions for the parent structures that have been flattened, and mark them as instances (if class)
557603 let lctx ← getLCtx
558604 let fieldsWithDefault := fieldInfos.filter fun info => info.value?.isSome
559605 let defaultAuxDecls ← fieldsWithDefault.mapM fun info => do
0 commit comments