This PR addresses two review points on `IO.CancelToken`: * `set` now resolves the underlying promise *before* writing the `Bool` fast-path flag, so observing `isSet = true` implies any synchronously chained `onSet` callback has already run. The previous order (flag first, then resolve) was a subtle footgun: code seeing `isSet = true` could not rely on the cancellation task having fired. * The underlying promise and the task it produces are kept private. The prior `task : Task (Option Unit)` accessor is removed; consumers should use `onSet` to react to cancellation. A comment on the structure records that re-exposing the task in the future requires re-auditing the order in `set` for races between the promise and the `Bool` flag. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
240 lines
4.9 KiB
Text
240 lines
4.9 KiB
Text
import Lean
|
||
/-!
|
||
# Tests for delaboration of constants (and name unresolution)
|
||
-/
|
||
|
||
set_option linter.unusedVariables false
|
||
|
||
/-!
|
||
Make sure unresolution avoids conflicts.
|
||
-/
|
||
def A.B.x : Nat := 0
|
||
def A.B'.x : Nat := 0
|
||
|
||
/-- info: x : Nat -/
|
||
#guard_msgs in open A B in #check (A.B.x)
|
||
/-- info: B'.x : Nat -/
|
||
#guard_msgs in open A B in #check (A.B'.x)
|
||
/-- info: B.x : Nat -/
|
||
#guard_msgs in open A B B' in #check (A.B.x)
|
||
/-- info: B'.x : Nat -/
|
||
#guard_msgs in open A B B' in #check (A.B'.x)
|
||
|
||
/-!
|
||
Another conflict check.
|
||
-/
|
||
def B.x : Nat := 0
|
||
/-- info: A.B.x : Nat -/
|
||
#guard_msgs in open A in #check (A.B.x)
|
||
-- Resolution accepts this exact match.
|
||
/-- info: B.x : Nat -/
|
||
#guard_msgs in open A in #check (B.x)
|
||
|
||
/-!
|
||
Opening does not shadow the `x` that's already in `B`.
|
||
-/
|
||
namespace B
|
||
open A.B
|
||
/-- info: A.B.x : Nat -/
|
||
#guard_msgs in #check (_root_.A.B.x)
|
||
/-- info: x : Nat -/
|
||
#guard_msgs in #check (_root_.B.x)
|
||
/-- info: x : Nat -/
|
||
#guard_msgs in #check (x)
|
||
end B
|
||
|
||
/--
|
||
A global `x` needs the `_root_` qualifier.
|
||
-/
|
||
def x : Nat := 0
|
||
namespace B
|
||
/-- info: _root_.x : Nat -/
|
||
#guard_msgs in #check (_root_.x)
|
||
/-- info: x : Nat -/
|
||
#guard_msgs in #check (B.x)
|
||
end B
|
||
|
||
/-!
|
||
Name that shadows a local constant needs `_root_`.
|
||
-/
|
||
/--
|
||
trace: id : Nat
|
||
⊢ _root_.id 2 = 2
|
||
-/
|
||
#guard_msgs in
|
||
example (id : Nat) : _root_.id 2 = 2 := by
|
||
trace_state
|
||
rfl
|
||
|
||
/-!
|
||
Even with `id.{1}` notation, the name `id` must not shadow the local constant.
|
||
-/
|
||
/--
|
||
trace: id : Nat
|
||
⊢ Eq.{1} (_root_.id.{1} 2) 2
|
||
-/
|
||
#guard_msgs in
|
||
set_option pp.universes true in
|
||
example (id : Nat) : _root_.id 2 = 2 := by
|
||
trace_state
|
||
rfl
|
||
|
||
/-!
|
||
In `pp.fullNames` mode, local name shadowing is still checked.
|
||
-/
|
||
/--
|
||
trace: id : Nat
|
||
⊢ _root_.id 2 = 2
|
||
-/
|
||
#guard_msgs in
|
||
set_option pp.fullNames true in
|
||
example (id : Nat) : _root_.id 2 = 2 := by
|
||
trace_state
|
||
rfl
|
||
/-- info: `_root_.id -/
|
||
#guard_msgs in
|
||
variable (id : Nat) in
|
||
#eval Lean.Elab.Command.runTermElabM fun _ =>
|
||
Lean.unresolveNameGlobalAvoidingLocals ``id (fullNames := true)
|
||
|
||
/-!
|
||
`match` shadowing test. This used to print the first `match` arm as
|
||
`| Bool.true => 0`, which is incorrect, since `Bool.true` resolves to `MatchTest1.Bool.true`
|
||
-/
|
||
namespace MatchTest1
|
||
|
||
def Bool.true := 0
|
||
set_option linter.constructorNameAsVariable false in
|
||
def f (true : Bool) : Nat :=
|
||
match true with
|
||
| _root_.Bool.true => 0
|
||
| false => 1
|
||
|
||
/--
|
||
info: def MatchTest1.f : Bool → Nat :=
|
||
fun true =>
|
||
match true with
|
||
| _root_.Bool.true => 0
|
||
| false => 1
|
||
-/
|
||
#guard_msgs in
|
||
#print f
|
||
|
||
/-- info: Bool.true : Nat -/
|
||
#guard_msgs in #check (Bool.true)
|
||
|
||
end MatchTest1
|
||
|
||
/-!
|
||
`match` shadowing test. This used to print the first `match` arm as
|
||
`| Bool.true => 0`, which is incorrect, since `Bool.true` resolves to the local variable.
|
||
-/
|
||
namespace MatchTest2
|
||
|
||
set_option linter.constructorNameAsVariable false in
|
||
def f (true : Bool) : Nat :=
|
||
let Bool.true := 1
|
||
match true with
|
||
| _root_.Bool.true => 0
|
||
| false => 1
|
||
|
||
/--
|
||
info: def MatchTest2.f : Bool → Nat :=
|
||
fun true =>
|
||
have Bool.true := 1;
|
||
match true with
|
||
| _root_.Bool.true => 0
|
||
| false => 1
|
||
-/
|
||
#guard_msgs in
|
||
#print f
|
||
|
||
end MatchTest2
|
||
|
||
/-!
|
||
`match` shadowing test. Similar to `MatchTest2`, but `| Bool.false =>` is correct.
|
||
-/
|
||
namespace MatchTest3
|
||
|
||
set_option linter.constructorNameAsVariable false in
|
||
def f (true : Bool) :=
|
||
let Bool.true := true
|
||
let false := true
|
||
match true with
|
||
| _root_.Bool.true => false
|
||
| Bool.false =>
|
||
match true with
|
||
| Bool.false => false
|
||
| false => Bool.true
|
||
/--
|
||
info: def MatchTest3.f : Bool → Bool :=
|
||
fun true =>
|
||
have Bool.true := true;
|
||
have false := true;
|
||
match true with
|
||
| _root_.Bool.true => false
|
||
| Bool.false =>
|
||
match true with
|
||
| Bool.false => false
|
||
| false => Bool.true
|
||
-/
|
||
#guard_msgs in open Bool in #print f
|
||
|
||
end MatchTest3
|
||
|
||
namespace PrvTest.NS1
|
||
|
||
private abbrev fst : Nat × Nat → Nat := fun s => s.1
|
||
private abbrev snd : Nat × Nat → Nat := fun s => s.2
|
||
|
||
/-!
|
||
The private names get unresolved. Previously this would print as
|
||
`PrvTest.NS1.fst (x, x) = PrvTest.NS1.snd (x, x)`.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
⊢ fst (x, x) = snd (x, x)
|
||
-/
|
||
#guard_msgs in
|
||
example (x : Nat) : fst (x,x) = snd (x,x) := by
|
||
trace_state
|
||
rfl
|
||
|
||
end NS1
|
||
|
||
export NS1 (fst)
|
||
|
||
/-!
|
||
Private names get unresolved with respect to the namespace, and aliases are respected too.
|
||
-/
|
||
/--
|
||
trace: x : Nat
|
||
⊢ fst (x, x) = NS1.snd (x, x)
|
||
-/
|
||
#guard_msgs in
|
||
example (x : Nat) : fst (x,x) = NS1.snd (x,x) := by
|
||
trace_state
|
||
rfl
|
||
|
||
end PrvTest
|
||
|
||
/-!
|
||
The name `IO.CancelToken.promise✝` is a private imported name.
|
||
-/
|
||
/--
|
||
info: def IO.CancelToken.set : IO.CancelToken → BaseIO Unit :=
|
||
fun tk => do
|
||
IO.Promise.resolve () (IO.CancelToken.promise✝ tk)
|
||
ST.Ref.set (IO.CancelToken.setRef✝ tk) true
|
||
-/
|
||
#guard_msgs in #print IO.CancelToken.set
|
||
/-!
|
||
Even if `IO` is opened, it won't print as `CancelToken.promise✝`, but the full name.
|
||
-/
|
||
/--
|
||
info: def IO.CancelToken.set : CancelToken → BaseIO Unit :=
|
||
fun tk => do
|
||
Promise.resolve () (IO.CancelToken.promise✝ tk)
|
||
ST.Ref.set (IO.CancelToken.setRef✝ tk) true
|
||
-/
|
||
#guard_msgs in open IO in #print IO.CancelToken.set
|