Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 5 additions & 4 deletions src/Lean/Elab/BuiltinCommand.lean
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,17 @@ where go
private def addNamespace (header : Name) : CommandElabM Unit :=
addScopes (isNewNamespace := true) (isNoncomputable := false) (attrs := []) header

private def popScopes (numScopes : Nat) : CommandElabM Unit :=
for _ in *...numScopes do
popScope

def withNamespace {α} (ns : Name) (elabFn : CommandElabM α) : CommandElabM α := do
addNamespace ns
let a ← elabFn
modify fun s => { s with scopes := s.scopes.drop ns.getNumParts }
popScopes ns.getNumParts
pure a

private def popScopes (numScopes : Nat) : CommandElabM Unit :=
for _ in *...numScopes do
popScope

private def innermostScopeName? : List Scope → Option Name
| { header := "", .. } :: _ => none
| { header := h, .. } :: _ => some <| .mkSimple h
Expand Down
45 changes: 45 additions & 0 deletions tests/lean/run/issue12630.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Lean
open Lean Meta Elab Command

/-!
# `withNamespace` should correctly pop scopes after running

Issue: https://github.com/leanprover/lean4/issues/12630

`withNamespace` was not calling `popScopes` on the environment extensions,
causing `scoped syntax` declarations to leak keywords outside their scope.
-/

-- Test 1: `withNamespace` inside namespace shouldn't leak scoped syntax
namespace outer1

#eval do
withNamespace `inner do
logInfo "shouldn't affect anything"

scoped syntax "myterm1" : term

end outer1

-- Outside the namespace, `myterm1` should parse as an identifier, not a keyword.
#eval do
let stx ← `(term|myterm1)
unless stx.raw.isIdent do
throwError "expected identifier, got keyword"

-- Test 2: `withNamespace` before namespace shouldn't leak scoped syntax
#eval do
withNamespace `outer2 do
logInfo "shouldn't affect anything"

namespace outer2

scoped syntax "myterm2" : term

end outer2

-- Outside the namespace, `myterm2` should parse as an identifier, not a keyword.
#eval do
let stx ← `(term|myterm2)
unless stx.raw.isIdent do
throwError "expected identifier, got keyword"
Loading