Skip to content

Commit 132d329

Browse files
authored
fix: lengthen RPC reference field name (#719)
Client part of leanprover/lean4#12905. Fixes #712.
1 parent 9a7ed4a commit 132d329

File tree

11 files changed

+121
-86
lines changed

11 files changed

+121
-86
lines changed

lean4-infoview-api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@leanprover/infoview-api",
3-
"version": "0.11.0",
3+
"version": "0.12.0",
44
"description": "Types and API for @leanprover/infoview.",
55
"repository": {
66
"type": "git",
@@ -9,7 +9,7 @@
99
"scripts": {
1010
"watch": "tsc --watch",
1111
"watchTest": "tsc --watch",
12-
"build": "tsc",
12+
"build": "tsc --sourceMap false",
1313
"build:dev": "tsc"
1414
},
1515
"main": "dist/index",

lean4-infoview-api/src/infoviewApi.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ export type ModuleHierarchyOptions = {}
1111

1212
export type HighlightMatchesOptions = {}
1313

14+
export type RpcWireFormat = 'v0' | 'v1'
15+
1416
export interface RpcOptions {
15-
highlightMatchesProvider?: HighlightMatchesOptions | undefined
17+
highlightMatchesProvider?: HighlightMatchesOptions
18+
rpcWireFormat?: RpcWireFormat
1619
}
1720

1821
export interface LeanServerCapabilities {
19-
moduleHierarchyProvider?: ModuleHierarchyOptions | undefined
20-
rpcProvider?: RpcOptions | undefined
22+
moduleHierarchyProvider?: ModuleHierarchyOptions
23+
rpcProvider?: RpcOptions
2124
}
2225

2326
export type ExpectedTypeVisibility = 'Expanded by default' | 'Collapsed by default' | 'Hidden'

lean4-infoview-api/src/lspTypes.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,19 @@ declare const tag: unique symbol
6969
* which we need to be able to reference but which would be too large to
7070
* send over directly.
7171
*/
72-
export type RpcPtr<T> = { readonly [tag]: T; p: string }
72+
export type RpcPtr<T> = { readonly [tag]: T; __rpcref: string } | { readonly [tag]: T; p: string }
7373

7474
// eslint-disable-next-line @typescript-eslint/no-namespace
7575
export namespace RpcPtr {
7676
export function copy<T>(p: RpcPtr<T>): RpcPtr<T> {
77-
return { p: p.p } as RpcPtr<T>
77+
if ('p' in p) return { p: p.p } as RpcPtr<T>
78+
else return { __rpcref: p.__rpcref } as RpcPtr<T>
7879
}
7980

8081
/** Turns a reference into a unique string. Useful for React `key`s. */
8182
export function toKey(p: RpcPtr<any>): string {
82-
return p.p
83+
if ('p' in p) return p.p
84+
else return p.__rpcref
8385
}
8486
}
8587

lean4-infoview-api/src/rpcSessions.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import type { DocumentUri, Position, TextDocumentPositionParams } from 'vscode-languageserver-protocol'
2-
import { ClientRequestOptions } from './infoviewApi'
1+
import type {
2+
DocumentUri,
3+
Position,
4+
ServerCapabilities,
5+
TextDocumentPositionParams,
6+
} from 'vscode-languageserver-protocol'
7+
import { ClientRequestOptions, LeanServerCapabilities } from './infoviewApi'
38
import { RpcCallParams, RpcErrorCode, RpcPtr, RpcReleaseParams } from './lspTypes'
49

510
/**
@@ -83,6 +88,7 @@ class RpcSessionForFile {
8388
constructor(
8489
public uri: DocumentUri,
8590
public sessions: RpcSessions,
91+
private serverCapabilities: ServerCapabilities<LeanServerCapabilities>,
8692
) {
8793
this.sessionId = (async () => {
8894
try {
@@ -133,23 +139,34 @@ class RpcSessionForFile {
133139
* for future garbage collection.
134140
*
135141
* The function implements a form of "conservative garbage collection"
136-
* where it treats any subobject `{'p': v}` as a potential RPC reference.
137-
* Therefore `p` should not be used as a field name on the Lean side
142+
* where it treats any subobject `{'__rpcref': v}` as a potential RPC reference.
143+
* Therefore `__rpcref` should not be used as a field name on the Lean side
138144
* to prevent false positives.
139145
*
140-
* It is unclear if the false positives will become a big issue.
141146
* Earlier versions of the extension
142147
* had manually written registration functions for every type,
143148
* but those are a lot of boilerplate.
144149
* If we change back to that approach,
145150
* we should generate them automatically.
151+
* It would also be possible to move to a new RPC wire format
152+
* that specifies which values are references using metadata.
146153
*/
147154
registerRefs(o: any) {
148155
if (o instanceof Object) {
149-
if (Object.keys(o as {}).length === 1 && 'p' in o && typeof o.p !== 'object') {
150-
this.finalizers.register(o as {}, RpcPtr.copy(o as RpcPtr<any>))
156+
let isRef = false
157+
if (this.serverCapabilities.experimental?.rpcProvider?.rpcWireFormat === 'v1') {
158+
if (Object.keys(o as {}).length === 1 && '__rpcref' in o && typeof o.__rpcref === 'string') {
159+
this.finalizers.register(o as {}, RpcPtr.copy(o as RpcPtr<any>))
160+
isRef = true
161+
}
151162
} else {
152-
for (const v of Object.values(o as {})) this.registerRefs(v)
163+
if (Object.keys(o as {}).length === 1 && 'p' in o && typeof o.p === 'string') {
164+
this.finalizers.register(o as {}, RpcPtr.copy(o as RpcPtr<any>))
165+
isRef = true
166+
}
167+
}
168+
if (!isRef) {
169+
for (const v of Object.values(o)) this.registerRefs(v)
153170
}
154171
} else if (o instanceof Array) {
155172
for (const e of o) this.registerRefs(e)
@@ -240,9 +257,12 @@ export class RpcSessions {
240257

241258
constructor(public iface: RpcServerIface) {}
242259

243-
private connectCore(uri: DocumentUri): RpcSessionForFile {
260+
private connectCore(
261+
uri: DocumentUri,
262+
serverCapabilities: ServerCapabilities<LeanServerCapabilities>,
263+
): RpcSessionForFile {
244264
if (this.sessions.has(uri)) return this.sessions.get(uri) as RpcSessionForFile
245-
const sess = new RpcSessionForFile(uri, this)
265+
const sess = new RpcSessionForFile(uri, this, serverCapabilities)
246266
this.sessions.set(uri, sess)
247267
return sess
248268
}
@@ -254,8 +274,11 @@ export class RpcSessions {
254274
* A new session is only created if a fatal error occurs (the worker crashes)
255275
* or the session is closed manually (the file is closed).
256276
*/
257-
connect(pos: TextDocumentPositionParams): RpcSessionAtPos {
258-
return this.connectCore(pos.textDocument.uri).at(pos.position)
277+
connect(
278+
pos: TextDocumentPositionParams,
279+
serverCapabilities: ServerCapabilities<LeanServerCapabilities>,
280+
): RpcSessionAtPos {
281+
return this.connectCore(pos.textDocument.uri, serverCapabilities).at(pos.position)
259282
}
260283

261284
/** Closes the session for the given URI. */

lean4-infoview/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@leanprover/infoview",
3-
"version": "0.11.1",
3+
"version": "0.12.0",
44
"description": "An interactive display for the Lean 4 theorem prover.",
55
"repository": {
66
"type": "git",
@@ -57,7 +57,7 @@
5757
"typescript": "^5.4.5"
5858
},
5959
"dependencies": {
60-
"@leanprover/infoview-api": "~0.11.0",
60+
"@leanprover/infoview-api": "~0.12.0",
6161
"@vscode/codicons": "^0.0.40",
6262
"@vscode-elements/react-elements": "^0.5.0",
6363
"es-module-lexer": "^1.5.4",

lean4-infoview/src/infoview/main.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ function Main() {
5757
[],
5858
)
5959

60-
const serverVersion = useEventResult(
61-
ec.events.serverRestarted,
62-
result => new ServerVersion(result.serverInfo?.version ?? ''),
60+
const serverVersion = useEventResult(ec.events.serverRestarted, result =>
61+
ServerVersion.ofString(result.serverInfo?.version ?? ''),
6362
)
6463
const capabilities = useEventResult(ec.events.serverRestarted, result => result.capabilities)
6564
const serverStoppedResult = useEventResult(ec.events.serverStopped)

lean4-infoview/src/infoview/rpcSessions.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
DocumentUri,
66
TextDocumentPositionParams,
77
} from 'vscode-languageserver-protocol'
8-
import { EditorContext, EnvPosContext } from './contexts'
8+
import { CapabilityContext, EditorContext, EnvPosContext } from './contexts'
99
import { DocumentPosition, useClientNotificationEffect, useEvent } from './util'
1010

1111
const RpcSessionsContext = React.createContext<RpcSessions | undefined>(undefined)
@@ -46,20 +46,20 @@ export function WithRpcSessions({ children }: { children: React.ReactNode }) {
4646
return <RpcSessionsContext.Provider value={sessions}>{children}</RpcSessionsContext.Provider>
4747
}
4848

49-
const noCtxRpcSession: RpcSessionAtPos = {
50-
call: async () => {
51-
throw new Error('no RPC context set')
52-
},
53-
}
54-
55-
const noPosRpcSession: RpcSessionAtPos = {
56-
call: async () => {
57-
throw new Error('no position context set')
58-
},
49+
function errorRpcSession(err: string): RpcSessionAtPos {
50+
return {
51+
call: async () => {
52+
throw new Error(err)
53+
},
54+
}
5955
}
6056

6157
export function useRpcSessionAtTdpp(pos: TextDocumentPositionParams): RpcSessionAtPos {
62-
return React.useContext(RpcSessionsContext)?.connect(pos) || noCtxRpcSession
58+
const rsc = React.useContext(RpcSessionsContext)
59+
const cap = React.useContext(CapabilityContext)
60+
if (!rsc) return errorRpcSession('no RPC context set')
61+
if (!cap) return errorRpcSession('no capability context set')
62+
return rsc.connect(pos, cap)
6363
}
6464

6565
export function useRpcSessionAtPos(pos: DocumentPosition): RpcSessionAtPos {
@@ -73,7 +73,7 @@ export function useRpcSessionAtPos(pos: DocumentPosition): RpcSessionAtPos {
7373
* A future major release of @leanprover/infoview could remove this context
7474
* after it has been deprecated for a sufficiently long time.
7575
*/
76-
export const RpcContext = React.createContext<RpcSessionAtPos>(noCtxRpcSession)
76+
export const RpcContext = React.createContext<RpcSessionAtPos>(errorRpcSession('no RPC context set'))
7777

7878
/**
7979
* Retrieve an RPC session at {@link EnvPosContext},
@@ -82,8 +82,13 @@ export const RpcContext = React.createContext<RpcSessionAtPos>(noCtxRpcSession)
8282
*/
8383
export function useRpcSession(): RpcSessionAtPos {
8484
const pos = React.useContext(EnvPosContext)
85+
// Cannot deduplicate with `useRpcSessionAtTdpp`
86+
// because we'd only call it when `pos !== undefined`
87+
// but hooks must be called unconditionally.
8588
const rsc = React.useContext(RpcSessionsContext)
86-
if (!pos) return noPosRpcSession
87-
if (!rsc) return noCtxRpcSession
88-
return rsc.connect(DocumentPosition.toTdpp(pos))
89+
const cap = React.useContext(CapabilityContext)
90+
if (!pos) return errorRpcSession('no position context set')
91+
if (!rsc) return errorRpcSession('no RPC context set')
92+
if (!cap) return errorRpcSession('no capability context set')
93+
return rsc.connect(DocumentPosition.toTdpp(pos), cap)
8994
}

lean4-infoview/src/infoview/serverVersion.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,37 @@
44
*/
55

66
export class ServerVersion {
7-
major: number
8-
minor: number
9-
patch: number
7+
constructor(
8+
public major: number,
9+
public minor: number,
10+
public patch: number,
11+
) {}
1012

11-
constructor(version: string) {
13+
static ofString(version: string): ServerVersion {
1214
const vs = version.split('.')
1315
if (vs.length === 2) {
14-
this.major = parseInt(vs[0])
15-
this.minor = parseInt(vs[1])
16-
this.patch = 0
16+
return new ServerVersion(parseInt(vs[0]), parseInt(vs[1]), 0)
1717
} else if (vs.length === 3) {
18-
this.major = parseInt(vs[0])
19-
this.minor = parseInt(vs[1])
20-
this.patch = parseInt(vs[2])
18+
return new ServerVersion(parseInt(vs[0]), parseInt(vs[1]), parseInt(vs[2]))
2119
} else {
2220
throw new Error(`cannot parse Lean server version '${version}'`)
2321
}
2422
}
23+
24+
/** Is `this` version equal to `other`? */
25+
eq(other: ServerVersion): boolean {
26+
return this.major === other.major && this.minor === other.minor && this.patch === other.patch
27+
}
28+
29+
/** Is `this` version strictly below `other`? */
30+
lt(other: ServerVersion): boolean {
31+
if (this.major !== other.major) return this.major < other.major
32+
if (this.minor !== other.minor) return this.minor < other.minor
33+
return this.patch < other.patch
34+
}
35+
36+
/** Is `this` version either strictly below or equal to `other`? */
37+
le(other: ServerVersion): boolean {
38+
return this.lt(other) || this.eq(other)
39+
}
2540
}

package-lock.json

Lines changed: 17 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)