Skip to content

Commit 92dec7e

Browse files
authored
feat: allow structures to have non-bracketed binders (#8671)
This PR allow structures to have non-bracketed binders, making it consistent with `inductive`. The change allows the following to be written instead of having to write `S (n)`: ```lean structure S n where field : Fin n ```
1 parent b3a53d5 commit 92dec7e

File tree

6 files changed

+139
-38
lines changed

6 files changed

+139
-38
lines changed

src/Lean/Elab/Structure.lean

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace Structure
3030

3131
/-! Recall that the `structure command syntax is
3232
```
33-
leading_parser (structureTk <|> classTk) >> declId >> many Term.bracketedBinder >> Term.optType >> optional «extends» >> optional (" := " >> optional structCtor >> structFields)
33+
leading_parser (structureTk <|> classTk) >> declId >> optDeclSig >> optional «extends» >> optional (" := " >> optional structCtor >> structFields)
3434
```
3535
-/
3636

@@ -206,10 +206,10 @@ private def expandCtor (structStx : Syntax) (structModifiers : Modifiers) (struc
206206
let ref := structStx[1].mkSynthetic
207207
addDeclarationRangesFromSyntax declName ref
208208
pure { ref, declId := ref, modifiers := default, declName }
209-
if structStx[5].isNone then
209+
if structStx[4].isNone then
210210
useDefault
211211
else
212-
let optCtor := structStx[5][1]
212+
let optCtor := structStx[4][1]
213213
if optCtor.isNone then
214214
useDefault
215215
else
@@ -278,12 +278,12 @@ def structFields := leading_parser many (structExplicitBinder <|> struct
278278
```
279279
-/
280280
private def expandFields (structStx : Syntax) (structModifiers : Modifiers) (structDeclName : Name) : TermElabM (Array StructFieldView) := do
281-
if structStx[5][0].isToken ":=" then
281+
if structStx[4][0].isToken ":=" then
282282
-- https://github.com/leanprover/lean4/issues/5236
283283
let cmd := if structStx[0].getKind == ``Parser.Command.classTk then "class" else "structure"
284-
withRef structStx[0] <| Linter.logLintIf Linter.linter.deprecated structStx[5][0]
284+
withRef structStx[0] <| Linter.logLintIf Linter.linter.deprecated structStx[4][0]
285285
s!"{cmd} ... :=' has been deprecated in favor of '{cmd} ... where'."
286-
let fieldBinders := if structStx[5].isNone then #[] else structStx[5][2][0].getArgs
286+
let fieldBinders := if structStx[4].isNone then #[] else structStx[4][2][0].getArgs
287287
fieldBinders.foldlM (init := #[]) fun (views : Array StructFieldView) fieldBinder => withRef fieldBinder do
288288
let mut fieldBinder := fieldBinder
289289
if fieldBinder.getKind == ``Parser.Command.structSimpleBinder then
@@ -342,43 +342,45 @@ private def expandFields (structStx : Syntax) (structModifiers : Modifiers) (str
342342
}
343343

344344
/-
345-
leading_parser (structureTk <|> classTk) >> declId >> many Term.bracketedBinder >> Term.optType >> optional «extends» >>
345+
leading_parser (structureTk <|> classTk) >> declId >> optDeclSig >> optional «extends» >>
346346
optional (("where" <|> ":=") >> optional structCtor >> structFields) >> optDeriving
347347
348348
where
349349
def structParent := leading_parser optional (atomic (ident >> " : ")) >> termParser
350-
def «extends» := leading_parser " extends " >> sepBy1 structParent ", "
351-
def typeSpec := leading_parser " : " >> termParser
352-
def optType : Parser := optional typeSpec
350+
def «extends» := leading_parser " extends " >> sepBy1 structParent ", " >> optType
353351
354352
def structFields := leading_parser many (structExplicitBinder <|> structImplicitBinder <|> structInstBinder)
355353
def structCtor := leading_parser try (declModifiers >> ident >> " :: ")
356354
-/
357355
def structureSyntaxToView (modifiers : Modifiers) (stx : Syntax) : TermElabM StructView := do
356+
let stx := -- for bootstrap compatibility, remove this after stage0 update
357+
if stx.getNumArgs == 7
358+
then stx.setArgs #[stx[0], stx[1], .node .none ``Parser.Command.optDeclSig #[stx[2], stx[3]], stx[4], stx[5], stx[6]]
359+
else stx
358360
checkValidInductiveModifier modifiers
359361
let isClass := stx[0].getKind == ``Parser.Command.classTk
360362
let modifiers := if isClass then modifiers.addAttr { name := `class } else modifiers
361363
let declId := stx[1]
362364
let ⟨name, declName, levelNames⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers
363365
addDeclarationRangesForBuiltin declName modifiers.stx stx
364-
let binders := stx[2]
365-
let (optType, exts) ←
366+
let (binders, type?) := expandOptDeclSig stx[2]
367+
let exts := stx[3]
368+
let type? ←
366369
-- Compatibility mode for `structure S extends P : Type` syntax
367-
if stx[3].isNone && !stx[4].isNone && !stx[4][0][2].isNone then
368-
logWarningAt stx[4][0][2][0] "\
370+
if type?.isNone && !exts.isNone && !exts[0][2].isNone then
371+
logWarningAt exts[0][2][0] "\
369372
The syntax is now 'structure S : Type extends P' rather than 'structure S extends P : Type'.\n\n\
370373
The purpose of this change is to accommodate 'structure S extends toP : P' syntax for naming parent projections."
371-
pure (stx[4][0][2], stx[4])
374+
pure (some exts[0][2][0][1])
372375
else
373-
if !stx[4].isNone && !stx[4][0][2].isNone then
374-
logErrorAt stx[4][0][2][0] "\
376+
if !exts.isNone && !exts[0][2].isNone then
377+
logErrorAt exts[0][2][0] "\
375378
Unexpected additional resulting type. \
376379
The syntax is now 'structure S : Type extends P' rather than 'structure S extends P : Type'.\n\n\
377380
The purpose of this change is to accommodate 'structure S extends toP : P' syntax for naming parent projections."
378-
pure (stx[3], stx[4])
379-
let parents ← expandParents exts
380-
let derivingClasses ← getOptDerivingClasses stx[6]
381-
let type? := if optType.isNone then none else some optType[0][1]
381+
pure type?
382+
let parents ← expandParents exts
383+
let derivingClasses ← getOptDerivingClasses stx[5]
382384
let ctor ← expandCtor stx modifiers declName
383385
let fields ← expandFields stx modifiers declName
384386
fields.forM fun field => do

src/Lean/Linter/MissingDocs.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def checkDecl : SimpleHandler := fun stx => do
149149
if declModifiersPubNoDoc head then -- no doc string
150150
lintField rest[1][0] stx[1] "computed field"
151151
else if rest.getKind == ``«structure» then
152-
unless rest[5][2].isNone do
152+
unless rest[4][2].isNone do
153153
let redecls : Std.HashSet String.Pos :=
154154
(← get).infoState.trees.foldl (init := {}) fun s tree =>
155155
tree.foldInfo (init := s) fun _ info s =>
@@ -163,7 +163,7 @@ def checkDecl : SimpleHandler := fun stx => do
163163
if let some range := stx.getRange? then
164164
if redecls.contains range.start then return
165165
lintField parent stx "public field"
166-
for stx in rest[5][2][0].getArgs do
166+
for stx in rest[4][2][0].getArgs do
167167
let head := stx[0]
168168
if declModifiersPubNoDoc head then
169169
if stx.getKind == ``structSimpleBinder then

src/Lean/Linter/UnusedVariables.lean

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,14 @@ builtin_initialize
189189
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
190190
stack.matches [`null, none, `null, ``Lean.Parser.Command.variable])
191191

192-
/-- `structure Foo where unused : Nat` -/
193-
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
194-
stack.matches [`null, none, `null, ``Lean.Parser.Command.structure])
195-
196-
/-- `inductive Foo where | unused : Foo` -/
192+
/--
193+
* `structure Foo (unused : Nat)`
194+
* `inductive Foo (unused : Foo)`
195+
-/
197196
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
198-
stack.matches [`null, none, `null, none, ``Lean.Parser.Command.inductive] &&
199-
(stack[3]? |>.any fun (stx, pos) =>
200-
pos == 0 &&
201-
[``Lean.Parser.Command.optDeclSig, ``Lean.Parser.Command.declSig].any (stx.isOfKind ·)))
197+
stack.matches [`null, none, `null, ``Lean.Parser.Command.optDeclSig, none] &&
198+
(stack[4]? |>.any fun (stx, _) =>
199+
[``Lean.Parser.Command.structure, ``Lean.Parser.Command.inductive].any (stx.isOfKind ·)))
202200

203201
/--
204202
* `structure Foo where foo (unused : Nat) : Nat`

src/Lean/Parser/Command.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def «structure» := leading_parser
236236
(structureTk <|> classTk) >>
237237
-- Note: no error recovery here due to clashing with the `class abbrev` syntax
238238
declId >>
239-
ppIndent (many (ppSpace >> Term.bracketedBinder) >> Term.optType >> optional «extends») >>
239+
ppIndent (optDeclSig >> optional «extends») >>
240240
optional ((symbol " := " <|> " where ") >> optional structCtor >> structFields) >>
241241
optDeriving
242242
@[builtin_command_parser] def declaration := leading_parser

tests/lean/1705.lean.expected.out

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
1705.lean:1:13-1:16: error: function expected at
2-
T
3-
term has type
4-
?m
5-
1705.lean:1:18-1:19: error: unexpected identifier; expected command
1+
1705.lean:1:22-1:23: error: type expected, got
2+
(R : ?m)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/-!
2+
# Testing structure declarations, including unbracketed parameters
3+
-/
4+
5+
/-!
6+
Structure with no parameters, no type, named constructor, and derived instance
7+
-/
8+
structure NatPair where
9+
mkPair ::
10+
fst : Nat
11+
snd : Nat
12+
deriving Inhabited
13+
14+
/-- info: NatPair : Type -/
15+
#guard_msgs in
16+
#check NatPair
17+
18+
/-- info: NatPair.mkPair (fst snd : Nat) : NatPair -/
19+
#guard_msgs in
20+
#check NatPair.mkPair
21+
22+
/-- info: instInhabitedNatPair -/
23+
#guard_msgs in
24+
#synth Inhabited NatPair
25+
26+
/-!
27+
Structure with type and no parameters
28+
-/
29+
structure Pointed : Type u where
30+
α : Sort u
31+
x : α
32+
33+
/-- info: Pointed.{u} : Type u -/
34+
#guard_msgs in
35+
#check Pointed
36+
37+
/-- info: Pointed.mk.{u} (α : Sort u) (x : α) : Pointed -/
38+
#guard_msgs in
39+
#check Pointed.mk
40+
41+
/-!
42+
Structure with unbracketed parameter, no type, and derived instance
43+
-/
44+
structure Δ n where
45+
val : Fin (n + 1)
46+
deriving Inhabited
47+
48+
/-- info: Δ (n : Nat) : Type -/
49+
#guard_msgs in
50+
#check Δ
51+
52+
/-- info: Δ.mk {n : Nat} (val : Fin (n + 1)) : Δ n -/
53+
#guard_msgs in
54+
#check Δ.mk
55+
56+
/-- info: @instInhabitedΔ -/
57+
#guard_msgs in
58+
#synth ∀ n, Inhabited (Δ n)
59+
60+
/-!
61+
Structure with unbracketed parameters, type, and extends
62+
-/
63+
structure Prod₃ α β γ : Type u extends toProd : α × β where
64+
trd : (γ : Type u)
65+
66+
/-- info: Prod₃.{u} (α β γ : Type u) : Type u -/
67+
#guard_msgs in
68+
#check Prod₃
69+
70+
/-- info: Prod₃.mk.{u} {α β γ : Type u} (toProd : α × β) (trd : γ) : Prod₃ α β γ -/
71+
#guard_msgs in
72+
#check Prod₃.mk
73+
74+
/-- info: Prod₃.toProd.{u} {α β γ : Type u} (self : Prod₃ α β γ) : α × β -/
75+
#guard_msgs in
76+
#check Prod₃.toProd
77+
78+
/-!
79+
Structure with no type and mixture of bracketed and unbracketed parameters
80+
-/
81+
structure IsLt {n} (i : Fin n) j where
82+
pf : i < j
83+
84+
/-- info: IsLt {n : Nat} (i j : Fin n) : Prop -/
85+
#guard_msgs in
86+
#check IsLt
87+
88+
/-- info: IsLt.mk {n : Nat} {i j : Fin n} (pf : i < j) : IsLt i j -/
89+
#guard_msgs in
90+
#check IsLt.mk
91+
92+
/-!
93+
Structure with type and mixture of bracketed and unbracketed parameters
94+
-/
95+
structure Eval {n} (F : Fin n → Sort u) i : Sort (max 1 u) where
96+
value : F i
97+
98+
/-- info: Eval.{u} {n : Nat} (F : Fin n → Sort u) (i : Fin n) : Sort (max 1 u) -/
99+
#guard_msgs in
100+
#check Eval
101+
102+
/-- info: Eval.mk.{u} {n : Nat} {F : Fin n → Sort u} {i : Fin n} (value : F i) : Eval F i -/
103+
#guard_msgs in
104+
#check Eval.mk

0 commit comments

Comments
 (0)