Skip to content

Commit fdce7a9

Browse files
committed
feat: structure diamonds basic support
See TODO in the new comments.
1 parent 56c2f59 commit fdce7a9

5 files changed

Lines changed: 125 additions & 34 deletions

File tree

src/Lean/Elab/Structure.lean

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Lean.Parser.Command
77
import Lean.Meta.Closure
88
import Lean.Meta.SizeOf
99
import Lean.Meta.Injective
10+
import Lean.Meta.Structure
1011
import Lean.Elab.Command
1112
import Lean.Elab.DeclModifiers
1213
import 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-
238231
private def findFieldInfo? (infos : Array StructFieldInfo) (fieldName : Name) : Option StructFieldInfo :=
239232
infos.find? fun info => info.name == fieldName
240233

241234
private 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+
244250
private 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

281326
private 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

tests/lean/diamond1.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
structure Bar (α : Type) where
2+
a : α
3+
b : Nat → α
4+
5+
structure Baz (α : Type) where
6+
a : α → α
7+
c : Bool → α
8+
d : Nat
9+
10+
set_option structureDiamondWarning false in
11+
structure Foo (α : Type) extends Bar α, Baz α -- Error: parent field type mismatch
12+
13+
set_option structureDiamondWarning false in
14+
structure Foo (α : Type) extends Bar (α → α), Baz α
15+
16+
#print Foo
17+
18+
def f (x : Nat) : Foo Nat :=
19+
{ a := fun y => x + y
20+
b := (. + .)
21+
c := fun _ => x
22+
d := x }
23+
24+
#print f
25+
26+
set_option structureDiamondWarning true in
27+
structure Foo' (α : Type) extends Bar (α → α), Baz α -- Warning: parent field duplication
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
diamond1.lean:11:40-11:45: error: parent field type mismatch, field 'a' from parent 'Baz' has type
2+
α → α : Type
3+
but is expected to have type
4+
α : Type
5+
inductive Foo : Type → Type
6+
constructors:
7+
Foo.mk : {α : Type} → Bar (α → α) → (Bool → α) → Nat → Foo α
8+
def f : Nat → Foo Nat :=
9+
fun x => { toBar := { a := fun y => x + y, b := fun a a_1 => a + a_1 }, c := fun x_1 => x, d := x }
10+
diamond1.lean:27:47-27:52: warning: field 'a' from 'Baz' has already been declared

tests/lean/struct1.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ structure S : Nat := -- error expected Type
1111

1212
structure S extends Nat → Nat := -- error expected structure
1313
(x : Nat)
14-
15-
structure S extends A Nat, A Bool := -- error field toA already declared
14+
set_option structureDiamondWarning true in
15+
structure S' extends A Nat, A Bool := -- error field toA already declared
1616
(x : Nat)
1717

1818
structure S extends A Nat, B Bool := -- error field `x` from `B` has already been declared

tests/lean/struct1.lean.expected.out

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
struct1.lean:9:14-9:17: error: expected Type
22
struct1.lean:12:20-12:29: error: expected structure
3-
struct1.lean:15:27-15:33: error: field 'toA' has already been declared
4-
struct1.lean:18:27-18:33: error: field 'x' from 'B' has already been declared
3+
struct1.lean:15:28-15:34: warning: field 'x' from 'A' has already been declared
4+
struct1.lean:15:28-15:34: error: parent field type mismatch, field 'x' from parent 'A' has type
5+
Bool : Type
6+
but is expected to have type
7+
Nat : Type
8+
struct1.lean:18:27-18:33: warning: field 'x' from 'B' has already been declared
9+
struct1.lean:18:27-18:33: error: parent field type mismatch, field 'x' from parent 'B' has type
10+
Bool : Type
11+
but is expected to have type
12+
Nat : Type
513
struct1.lean:29:1-29:2: error: field 'x' has already been declared
614
struct1.lean:32:1-32:2: error: field 'x' has been declared in parent structure
715
struct1.lean:35:6-35:10: error: type mismatch

0 commit comments

Comments
 (0)