Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions src/Lean/Elab/Structure.lean
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace Structure

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

Expand Down Expand Up @@ -206,10 +206,10 @@ private def expandCtor (structStx : Syntax) (structModifiers : Modifiers) (struc
let ref := structStx[1].mkSynthetic
addDeclarationRangesFromSyntax declName ref
pure { ref, declId := ref, modifiers := default, declName }
if structStx[5].isNone then
if structStx[4].isNone then
useDefault
else
let optCtor := structStx[5][1]
let optCtor := structStx[4][1]
if optCtor.isNone then
useDefault
else
Expand Down Expand Up @@ -278,12 +278,12 @@ def structFields := leading_parser many (structExplicitBinder <|> struct
```
-/
private def expandFields (structStx : Syntax) (structModifiers : Modifiers) (structDeclName : Name) : TermElabM (Array StructFieldView) := do
if structStx[5][0].isToken ":=" then
if structStx[4][0].isToken ":=" then
-- https://github.com/leanprover/lean4/issues/5236
let cmd := if structStx[0].getKind == ``Parser.Command.classTk then "class" else "structure"
withRef structStx[0] <| Linter.logLintIf Linter.linter.deprecated structStx[5][0]
withRef structStx[0] <| Linter.logLintIf Linter.linter.deprecated structStx[4][0]
s!"{cmd} ... :=' has been deprecated in favor of '{cmd} ... where'."
let fieldBinders := if structStx[5].isNone then #[] else structStx[5][2][0].getArgs
let fieldBinders := if structStx[4].isNone then #[] else structStx[4][2][0].getArgs
fieldBinders.foldlM (init := #[]) fun (views : Array StructFieldView) fieldBinder => withRef fieldBinder do
let mut fieldBinder := fieldBinder
if fieldBinder.getKind == ``Parser.Command.structSimpleBinder then
Expand Down Expand Up @@ -342,43 +342,45 @@ private def expandFields (structStx : Syntax) (structModifiers : Modifiers) (str
}

/-
leading_parser (structureTk <|> classTk) >> declId >> many Term.bracketedBinder >> Term.optType >> optional «extends» >>
leading_parser (structureTk <|> classTk) >> declId >> optDeclSig >> optional «extends» >>
optional (("where" <|> ":=") >> optional structCtor >> structFields) >> optDeriving

where
def structParent := leading_parser optional (atomic (ident >> " : ")) >> termParser
def «extends» := leading_parser " extends " >> sepBy1 structParent ", "
def typeSpec := leading_parser " : " >> termParser
def optType : Parser := optional typeSpec
def «extends» := leading_parser " extends " >> sepBy1 structParent ", " >> optType

def structFields := leading_parser many (structExplicitBinder <|> structImplicitBinder <|> structInstBinder)
def structCtor := leading_parser try (declModifiers >> ident >> " :: ")
-/
def structureSyntaxToView (modifiers : Modifiers) (stx : Syntax) : TermElabM StructView := do
let stx := -- for bootstrap compatibility, remove this after stage0 update
if stx.getNumArgs == 7
then stx.setArgs #[stx[0], stx[1], .node .none ``Parser.Command.optDeclSig #[stx[2], stx[3]], stx[4], stx[5], stx[6]]
else stx
checkValidInductiveModifier modifiers
let isClass := stx[0].getKind == ``Parser.Command.classTk
let modifiers := if isClass then modifiers.addAttr { name := `class } else modifiers
let declId := stx[1]
let ⟨name, declName, levelNames⟩ ← Term.expandDeclId (← getCurrNamespace) (← Term.getLevelNames) declId modifiers
addDeclarationRangesForBuiltin declName modifiers.stx stx
let binders := stx[2]
let (optType, exts) ←
let (binders, type?) := expandOptDeclSig stx[2]
let exts := stx[3]
let type? ←
-- Compatibility mode for `structure S extends P : Type` syntax
if stx[3].isNone && !stx[4].isNone && !stx[4][0][2].isNone then
logWarningAt stx[4][0][2][0] "\
if type?.isNone && !exts.isNone && !exts[0][2].isNone then
logWarningAt exts[0][2][0] "\
The syntax is now 'structure S : Type extends P' rather than 'structure S extends P : Type'.\n\n\
The purpose of this change is to accommodate 'structure S extends toP : P' syntax for naming parent projections."
pure (stx[4][0][2], stx[4])
pure (some exts[0][2][0][1])
else
if !stx[4].isNone && !stx[4][0][2].isNone then
logErrorAt stx[4][0][2][0] "\
if !exts.isNone && !exts[0][2].isNone then
logErrorAt exts[0][2][0] "\
Unexpected additional resulting type. \
The syntax is now 'structure S : Type extends P' rather than 'structure S extends P : Type'.\n\n\
The purpose of this change is to accommodate 'structure S extends toP : P' syntax for naming parent projections."
pure (stx[3], stx[4])
let parents ← expandParents exts
let derivingClasses ← getOptDerivingClasses stx[6]
let type? := if optType.isNone then none else some optType[0][1]
pure type?
let parents ← expandParents exts
let derivingClasses ← getOptDerivingClasses stx[5]
let ctor ← expandCtor stx modifiers declName
let fields ← expandFields stx modifiers declName
fields.forM fun field => do
Expand Down
4 changes: 2 additions & 2 deletions src/Lean/Linter/MissingDocs.lean
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def checkDecl : SimpleHandler := fun stx => do
if declModifiersPubNoDoc head then -- no doc string
lintField rest[1][0] stx[1] "computed field"
else if rest.getKind == ``«structure» then
unless rest[5][2].isNone do
unless rest[4][2].isNone do
let redecls : Std.HashSet String.Pos :=
(← get).infoState.trees.foldl (init := {}) fun s tree =>
tree.foldInfo (init := s) fun _ info s =>
Expand All @@ -163,7 +163,7 @@ def checkDecl : SimpleHandler := fun stx => do
if let some range := stx.getRange? then
if redecls.contains range.start then return
lintField parent stx "public field"
for stx in rest[5][2][0].getArgs do
for stx in rest[4][2][0].getArgs do
let head := stx[0]
if declModifiersPubNoDoc head then
if stx.getKind == ``structSimpleBinder then
Expand Down
16 changes: 7 additions & 9 deletions src/Lean/Linter/UnusedVariables.lean
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,14 @@ builtin_initialize
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
stack.matches [`null, none, `null, ``Lean.Parser.Command.variable])

/-- `structure Foo where unused : Nat` -/
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
stack.matches [`null, none, `null, ``Lean.Parser.Command.structure])

/-- `inductive Foo where | unused : Foo` -/
/--
* `structure Foo (unused : Nat)`
* `inductive Foo (unused : Foo)`
-/
builtin_initialize addBuiltinUnusedVariablesIgnoreFn (fun _ stack _ =>
stack.matches [`null, none, `null, none, ``Lean.Parser.Command.inductive] &&
(stack[3]? |>.any fun (stx, pos) =>
pos == 0 &&
[``Lean.Parser.Command.optDeclSig, ``Lean.Parser.Command.declSig].any (stx.isOfKind ·)))
Copy link
Collaborator

@kmill kmill Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any idea why it was looking for declSig? I looked through git history and, even when this was first introduced (maybe in this commit) the parser used optDeclSig.

As far as I can tell, inductive always used optDeclSig.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know for sure. My guess is that isInInductive in the original commit was copied from isInDeclarationSignature without simplification.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look. The PR looks good, and I'll add it to the merge queue, thanks!

stack.matches [`null, none, `null, ``Lean.Parser.Command.optDeclSig, none] &&
(stack[4]? |>.any fun (stx, _) =>
[``Lean.Parser.Command.structure, ``Lean.Parser.Command.inductive].any (stx.isOfKind ·)))

/--
* `structure Foo where foo (unused : Nat) : Nat`
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Parser/Command.lean
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def «structure» := leading_parser
(structureTk <|> classTk) >>
-- Note: no error recovery here due to clashing with the `class abbrev` syntax
declId >>
ppIndent (many (ppSpace >> Term.bracketedBinder) >> Term.optType >> optional «extends») >>
ppIndent (optDeclSig >> optional «extends») >>
optional ((symbol " := " <|> " where ") >> optional structCtor >> structFields) >>
optDeriving
@[builtin_command_parser] def declaration := leading_parser
Expand Down
7 changes: 2 additions & 5 deletions tests/lean/1705.lean.expected.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
1705.lean:1:13-1:16: error: function expected at
T
term has type
?m
1705.lean:1:18-1:19: error: unexpected identifier; expected command
1705.lean:1:22-1:23: error: type expected, got
(R : ?m)
18 changes: 18 additions & 0 deletions tests/lean/run/structBinderIdent.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
structure Δ n where
val : Fin (n + 1)
deriving Inhabited

/--
info: Δ (n : Nat) : Type
-/
#guard_msgs in
#check Δ

structure Prod₃ α β γ : Type u extends toProd : α × β where
trd : (γ : Type u)

/--
info: Prod₃.{u} (α β γ : Type u) : Type u
-/
#guard_msgs in
#check Prod₃
Loading