Skip to content

Commit 3953755

Browse files
committed
Merge branch 'master' of https://github.com/leanprover/lean4 into joachim/issue10821
2 parents b9c61b4 + f9adafe commit 3953755

File tree

20 files changed

+297
-72
lines changed

20 files changed

+297
-72
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ jobs:
116116
# rerun the workflow run after setting the `release-ci`/`merge-ci` labels.
117117
run: |
118118
check_level=0
119+
fast=false
119120
120121
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
121122
check_level=2
@@ -128,9 +129,13 @@ jobs:
128129
elif echo "$labels" | grep -q "merge-ci"; then
129130
check_level=1
130131
fi
132+
if echo "$labels" | grep -q "fast-ci"; then
133+
fast=true
134+
fi
131135
fi
132136
133137
echo "check-level=$check_level" >> "$GITHUB_OUTPUT"
138+
echo "fast=$fast" >> "$GITHUB_OUTPUT"
134139
env:
135140
GH_TOKEN: ${{ github.token }}
136141

@@ -140,7 +145,8 @@ jobs:
140145
with:
141146
script: |
142147
const level = ${{ steps.set-level.outputs.check-level }};
143-
console.log(`level: ${level}`);
148+
const fast = ${{ steps.set-level.outputs.fast }};
149+
console.log(`level: ${level}, fast: ${fast}`);
144150
// use large runners where available (original repo)
145151
let large = ${{ github.repository == 'leanprover/lean4' }};
146152
const isPr = "${{ github.event_name }}" == "pull_request";
@@ -165,7 +171,8 @@ jobs:
165171
{
166172
// portable release build: use channel with older glibc (2.26)
167173
"name": "Linux release",
168-
"os": "ubuntu-latest",
174+
// usually not a bottleneck so make exclusive to `fast-ci`
175+
"os": large && fast ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
169176
"release": true,
170177
// Special handling for release jobs. We want:
171178
// 1. To run it in PRs so developers get PR toolchains (so secondary without tests is sufficient)
@@ -230,22 +237,22 @@ jobs:
230237
{
231238
"name": "macOS aarch64",
232239
// standard GH runner only comes with 7GB so use large runner if possible when running tests
233-
"os": large && level >= 1 ? "nscloud-macos-sequoia-arm64-6x14" : "macos-15",
240+
"os": large && (fast || level >= 1) ? "nscloud-macos-sequoia-arm64-6x14" : "macos-15",
234241
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
235242
"release": true,
236-
"test": true,
237243
"shell": "bash -euxo pipefail {0}",
238244
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-apple-darwin.tar.zst",
239245
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
240246
"binary-check": "otool -L",
241247
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
242248
// See "Linux release" for release job levels; Grove is not a concern here
243249
"enabled": isPr || level != 1,
250+
"test": level >= 1,
244251
"secondary": level == 0,
245252
},
246253
{
247254
"name": "Windows",
248-
"os": large && level == 2 ? "namespace-profile-windows-amd64-4x16" : "windows-2022",
255+
"os": large && (fast || level == 2) ? "namespace-profile-windows-amd64-4x16" : "windows-2022",
249256
"release": true,
250257
"enabled": level >= 2,
251258
"test": true,

src/Lean/Elab/App.lean

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,8 +1252,11 @@ inductive LValResolution where
12521252
The `baseName` is the base name of the type to search for in the parameter list. -/
12531253
| localRec (baseName : Name) (fvar : Expr)
12541254

1255+
private def mkLValError (e : Expr) (eType : Expr) (msg : MessageData) : MessageData :=
1256+
m!"{msg}{indentExpr e}\nhas type{indentExpr eType}"
1257+
12551258
private def throwLValErrorAt (ref : Syntax) (e : Expr) (eType : Expr) (msg : MessageData) : TermElabM α :=
1256-
throwErrorAt ref "{msg}{indentExpr e}\nhas type{indentExpr eType}"
1259+
throwErrorAt ref (mkLValError e eType msg)
12571260

12581261
private def throwLValError (e : Expr) (eType : Expr) (msg : MessageData) : TermElabM α := do
12591262
throwLValErrorAt (← getRef) e eType msg
@@ -1322,9 +1325,19 @@ where
13221325
| some (_, p2) => prodArity p2 + 1
13231326

13241327
private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM LValResolution := do
1325-
let throwInvalidFieldAt {α : Type} (ref : Syntax) (fieldName : String) (fullName : Name) : TermElabM α := do
1326-
throwLValErrorAt ref e eType <| ← mkUnknownIdentifierMessage (declHint := fullName)
1327-
m!"Invalid field `{fieldName}`: The environment does not contain `{fullName}`"
1328+
let throwInvalidFieldAt {α : Type} (ref : Syntax) (fieldName : String) (fullName : Name)
1329+
(declHint := Name.anonymous) : TermElabM α := do
1330+
let msg ←
1331+
-- ordering: put decl hint, if any, last
1332+
mkUnknownIdentifierMessage (declHint := declHint)
1333+
(mkLValError e eType
1334+
m!"Invalid field `{fieldName}`: The environment does not contain `{fullName}`")
1335+
-- HACK: Simulate previous embedding of tagged `mkUnknownIdentifierMessage`.
1336+
-- The "import unknown identifier" code action requires the tag to be present somewhere in the
1337+
-- message. But if it is at the root, the tag will also be shown to the user even though the
1338+
-- current help page does not address field notation, which should likely get its own help page
1339+
-- (if any).
1340+
throwErrorAt ref m!"{msg}{.nil}"
13281341
if eType.isForall then
13291342
match lval with
13301343
| LVal.fieldName ref fieldName suffix? _fullRef =>
@@ -1390,6 +1403,9 @@ private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM L
13901403
if let some (baseStructName, fullName) ← findMethod? structName (.mkSimple fieldName) then
13911404
return LValResolution.const baseStructName structName fullName
13921405
throwInvalidFieldAt ref fieldName fullName
1406+
-- Suggest a potential unreachable private name as hint. This does not cover structure
1407+
-- inheritance, nor `import all`.
1408+
(declHint := (mkPrivateName env structName).mkStr fieldName)
13931409
| none, LVal.fieldName ref _ (some suffix) _fullRef =>
13941410
-- This may be a function constant whose implicit arguments have already been filled in:
13951411
let c := e.getAppFn

src/Lean/Elab/Term/TermElabM.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,7 @@ where
19711971
let env ← getEnv
19721972
-- check for scope errors before trying auto implicits
19731973
if env.isExporting then
1974-
if let [(npriv, _)] ← withEnv (env.setExporting false) <| resolveGlobalName (enableLog := false) n then
1974+
if let [(npriv, _)] ← withoutExporting <| resolveGlobalName (enableLog := false) n then
19751975
throwUnknownIdentifierAt (declHint := npriv) stx m!"Unknown identifier `{.ofConstName n}`"
19761976
if (← read).autoBoundImplicit &&
19771977
!(← read).autoBoundImplicitForbidden n &&

src/Lean/Exception.lean

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,8 @@ function directly.
105105
protected def «throwNamedErrorAt» [Monad m] [MonadError m] (ref : Syntax) (name : Name) (msg : MessageData) : m α :=
106106
withRef ref <| Lean.throwNamedError name msg
107107

108-
/--
109-
Creates a `MessageData` that is tagged with `unknownIdentifierMessageTag`.
110-
This tag is used by the 'import unknown identifier' code action to detect messages that should
111-
prompt the code action.
112-
The end position of the range of an unknown identifier message should always point at the end of the
113-
unknown identifier.
114-
115-
If `declHint` is specified, a corresponding hint is added to the message in case the name refers to
116-
a private declaration that is not accessible in the current context.
117-
-/
118-
def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
108+
/-- Like `mkUnknownIdentifierMessage`, but does not tag the message. -/
109+
def mkUnknownIdentifierMessageCore [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
119110
(declHint := Name.anonymous) : m MessageData := do
120111
let mut msg := msg
121112
let env ← getEnv
@@ -131,6 +122,21 @@ def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : Mess
131122
msg ++ .note m!"A private declaration `{c}` (from `{mod}`) exists but would need to be public to access here."
132123
else
133124
msg ++ .note m!"A public declaration `{c}` exists but is imported privately; consider adding `public import {mod}`."
125+
return msg
126+
127+
/--
128+
Creates a `MessageData` that is tagged with `unknownIdentifierMessageTag`.
129+
This tag is used by the 'import unknown identifier' code action to detect messages that should
130+
prompt the code action.
131+
The end position of the range of an unknown identifier message should always point at the end of the
132+
unknown identifier.
133+
134+
If `declHint` is specified, a corresponding hint is added to the message in case the name refers to
135+
a private declaration that is not accessible in the current context.
136+
-/
137+
def mkUnknownIdentifierMessage [Monad m] [MonadEnv m] [MonadError m] (msg : MessageData)
138+
(declHint := Name.anonymous) : m MessageData := do
139+
let msg ← mkUnknownIdentifierMessageCore msg declHint
134140
return MessageData.tagged unknownIdentifierMessageTag msg
135141

136142
/--

src/Std/Internal/Async/Signal.lean

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,23 +238,26 @@ def stop (s : Signal.Waiter) : IO Unit :=
238238
s.native.stop
239239

240240
/--
241-
Create a `Selector` that resolves once `s` has received the signal. Note that calling this function starts `s`
242-
if it hasn't already started.
241+
Create a `Selector` that resolves once `s` has received the signal. Note that calling this function
242+
does not start the signal waiter.
243243
-/
244-
def selector (s : Signal.Waiter) : IO (Selector Unit) := do
245-
let signalWaiter ← s.wait
246-
return {
244+
def selector (s : Signal.Waiter) : Selector Unit :=
245+
{
247246
tryFn := do
247+
let signalWaiter : AsyncTask _ ← async s.wait
248248
if ← IO.hasFinished signalWaiter then
249249
return some ()
250250
else
251+
s.native.cancel
251252
return none
252253

253254
registerFn waiter := do
255+
let signalWaiter ← s.wait
254256
discard <| AsyncTask.mapIO (x := signalWaiter) fun _ => do
255257
let lose := return ()
256258
let win promise := promise.resolve (.ok ())
257259
waiter.race lose win
258260

259-
unregisterFn := s.stop
261+
unregisterFn := s.native.cancel
262+
260263
}

src/Std/Internal/Async/TCP.lean

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,44 @@ def accept (s : Server) : Async Client := do
6969
|> Async.ofPromise
7070
|>.map Client.ofNative
7171

72+
/--
73+
Tries to accept an incoming connection.
74+
-/
75+
@[inline]
76+
def tryAccept (s : Server) : IO (Option Client) := do
77+
let res ← s.native.tryAccept
78+
let socket ← IO.ofExcept res
79+
return Client.ofNative <$> socket
80+
81+
/--
82+
Creates a `Selector` that resolves once `s` has a connection available. Calling this function
83+
does not start the connection wait, so it must not be called in parallel with `accept`.
84+
-/
85+
def acceptSelector (s : TCP.Socket.Server) : Selector Client :=
86+
{
87+
tryFn :=
88+
s.tryAccept
89+
90+
registerFn waiter := do
91+
let task ← s.native.accept
92+
93+
-- If we get cancelled the promise will be dropped so prepare for that
94+
IO.chainTask (t := task.result?) fun res => do
95+
match res with
96+
| none => return ()
97+
| some res =>
98+
let lose := return ()
99+
let win promise := do
100+
try
101+
let result ← IO.ofExcept res
102+
promise.resolve (.ok (Client.ofNative result))
103+
catch e =>
104+
promise.resolve (.error e)
105+
waiter.race lose win
106+
107+
unregisterFn := s.native.cancelAccept
108+
}
109+
72110
/--
73111
Gets the local address of the server socket.
74112
-/
@@ -143,20 +181,25 @@ def recv? (s : Client) (size : UInt64) : Async (Option ByteArray) :=
143181

144182
/--
145183
Creates a `Selector` that resolves once `s` has data available, up to at most `size` bytes,
146-
and provides that data. Calling this function starts the data wait, so it must not be called
184+
and provides that data. Calling this function does not starts the data wait, so it must not be called
147185
in parallel with `recv?`.
148186
-/
149-
def recvSelector (s : TCP.Socket.Client) (size : UInt64) : Async (Selector (Option ByteArray)) := do
150-
let readableWaiter ← s.native.waitReadable
151-
return {
187+
def recvSelector (s : TCP.Socket.Client) (size : UInt64) : Selector (Option ByteArray) :=
188+
{
152189
tryFn := do
190+
let readableWaiter ← s.native.waitReadable
191+
153192
if ← readableWaiter.isResolved then
154193
-- We know that this read should not block
155194
let res ← (s.recv? size).block
156195
return some res
157196
else
197+
s.native.cancelRecv
158198
return none
199+
159200
registerFn waiter := do
201+
let readableWaiter ← s.native.waitReadable
202+
160203
-- If we get cancelled the promise will be dropped so prepare for that
161204
discard <| IO.mapTask (t := readableWaiter.result?) fun res => do
162205
match res with
@@ -172,6 +215,7 @@ def recvSelector (s : TCP.Socket.Client) (size : UInt64) : Async (Selector (Opti
172215
catch e =>
173216
promise.resolve (.error e)
174217
waiter.race lose win
218+
175219
unregisterFn := s.native.cancelRecv
176220
}
177221

src/Std/Internal/Async/Timer.lean

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,18 @@ def stop (s : Sleep) : IO Unit :=
6969
s.native.stop
7070

7171
/--
72-
Create a `Selector` that resolves once `s` has finished. Note that calling this function starts `s`
73-
if it hasn't already started.
72+
Create a `Selector` that resolves once `s` has finished. `s` only starts when it runs inside of a Selectable.
7473
-/
75-
def selector (s : Sleep) : Async (Selector Unit) := do
76-
return {
74+
def selector (s : Sleep) : Selector Unit :=
75+
{
7776
tryFn := do
7877
let sleepWaiter ← s.native.next
7978
if ← sleepWaiter.isResolved then
8079
return some ()
8180
else
81+
s.native.cancel
8282
return none
83+
8384
registerFn waiter := do
8485
let sleepWaiter ← s.native.next
8586
BaseIO.chainTask sleepWaiter.result? fun
@@ -107,7 +108,7 @@ Return a `Selector` that completes after `duration`.
107108
-/
108109
def Selector.sleep (duration : Std.Time.Millisecond.Offset) : Async (Selector Unit) := do
109110
let sleeper ← Sleep.mk duration
110-
sleeper.selector
111+
return sleeper.selector
111112

112113
/--
113114
`Interval` can be used to repeatedly wait for some duration like a clock.

src/Std/Internal/Async/UDP.lean

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,24 @@ def recv (s : Socket) (size : UInt64) : Async (ByteArray × Option SocketAddress
9191
Creates a `Selector` that resolves once `s` has data available, up to at most `size` bytes,
9292
and provides that data. If the socket has not been previously bound with `bind`, it is
9393
automatically bound to `0.0.0.0` (all interfaces) with a random port.
94-
Calling this function starts the data wait, so it must not be called in parallel with `recv`.
94+
Calling this function does starts the data wait, only when it's used with `Selectable.one` or `combine`.
95+
It must not be called in parallel with `recv`.
9596
-/
96-
def recvSelector (s : Socket) (size : UInt64) :
97-
Async (Selector (ByteArray × Option SocketAddress)) := do
98-
let readableWaiter ← s.native.waitReadable
99-
return {
97+
def recvSelector (s : Socket) (size : UInt64) : Selector (ByteArray × Option SocketAddress) :=
98+
{
10099
tryFn := do
100+
let readableWaiter ← s.native.waitReadable
101+
101102
if ← readableWaiter.isResolved then
102103
-- We know that this read should not block
103104
let res ← (s.recv size).block
104105
return some res
105106
else
107+
s.native.cancelRecv
106108
return none
107109
registerFn waiter := do
110+
let readableWaiter ← s.native.waitReadable
111+
108112
-- If we get cancelled the promise will be dropped so prepare for that
109113
discard <| IO.mapTask (t := readableWaiter.result?) fun res => do
110114
match res with
@@ -120,6 +124,7 @@ def recvSelector (s : Socket) (size : UInt64) :
120124
catch e =>
121125
promise.resolve (.error e)
122126
waiter.race lose win
127+
123128
unregisterFn := s.native.cancelRecv
124129
}
125130

src/Std/Internal/UV/Signal.lean

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ This function has different behavior depending on the state of the `Signal`:
7777
@[extern "lean_uv_signal_stop"]
7878
opaque stop (signal : @& Signal) : IO Unit
7979

80+
/--
81+
This function has different behavior depending on the state of the `Signal`:
82+
- If it is initial or finished this is a no-op.
83+
- If it's running then it drops the accept promise and if it's not repeatable it sets
84+
the signal handler to the initial state.
85+
-/
86+
@[extern "lean_uv_signal_cancel"]
87+
opaque cancel (signal : @& Signal) : IO Unit
88+
8089
end Signal
8190

8291
end UV

src/Std/Internal/UV/TCP.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ Accepts an incoming connection on a listening TCP socket.
9595
@[extern "lean_uv_tcp_accept"]
9696
opaque accept (socket : @& Socket) : IO (IO.Promise (Except IO.Error Socket))
9797

98+
/--
99+
Tries to accept an incoming connection on a listening TCP socket.
100+
-/
101+
@[extern "lean_uv_tcp_try_accept"]
102+
opaque tryAccept (socket : @& Socket) : IO (Except IO.Error (Option Socket))
103+
104+
/--
105+
Cancels the accept request of a socket.
106+
-/
107+
@[extern "lean_uv_tcp_cancel_accept"]
108+
opaque cancelAccept (socket : @& Socket) : IO Unit
109+
98110
/--
99111
Shuts down an incoming connection on a listening TCP socket.
100112
-/

0 commit comments

Comments
 (0)