Skip to content

Commit 522b1ad

Browse files
Provide a unified (lsp-interface INTERFACE ...) pcase form (#4559)
* Provide a unified (lsp-interface INTERFACE ...) pcase form This commit provides a new unified pcase form (lsp-interface INTERFACE ...) to replace the old per-interface (INTERFACE ...) forms -- the latter are now deprecated. (Unfortunately, I don't think there's a way to mark a pcase form as obsolete.) I've turned the existing pcase-defmacro definition into a helper function. The new pcase form delegates to that helper function, and the old pcase forms now delegate to the new form. This change addresses a few issues, which are detailed in #4430. In short: * The existing forms aren't namespaced. * The lsp-mode package adds hundreds of forms, which each add several lines to pcase's generated docstring, adding up to over 1000 lines. * Starting in Emacs 31, the number of forms added by lsp-mode causes a noticeable slowdown when loading the interactive help for pcase. * Add a comment and TODO about deprecating per-interface pcase forms * Improve docstring for (lsp-interface ...) pcase form I've tried to summarize the behavior of the existing implementation. * Replace per-interface pcase forms with the new unified form I used ripgrep to search for all occurrences of `pcase`, and then checked each to see if it was using a pattern that looked like an LSP interface name. It's very possible I missed something, but hopefully not. * Remove the per-interface pcase forms The consensus in #4430 is that we're ok with making this breaking change and communicating it in the CHANGELOG. I've replaced all uses in lsp-mode itself. * Add CHANGELOG entry for pcase changes I'm an Org newb, so hopefully this is well-formatted.
1 parent a52ef29 commit 522b1ad

File tree

5 files changed

+58
-28
lines changed

5 files changed

+58
-28
lines changed

CHANGELOG.org

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
* Change ~ruff-lsp~ to ~ruff~ for python lsp client. All ~ruff-lsp~ customizable variable change to ~ruff~. Lsp server command now is ~["ruff" "server"]~ instead of ~["ruff-lsp"]~.
1515
* Add futhark support
1616
* Optimize overlay creation by checking window visibility first
17-
17+
* Replace the per-interface ~(INTERFACE ...)~ pcase forms with a single,
18+
unified ~(lsp-interface INTERFACE ...)~ form. The per-interface forms are no
19+
longer generated. *This is a breaking change.* (See #4430.)
1820

1921
** 9.0.0
2022
* Add language server config for QML (Qt Modeling Language) using qmlls.

lsp-completion.el

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,8 +575,8 @@ Others: CANDIDATES"
575575
(apply #'delete-region markers)
576576
(insert prefix)
577577
(pcase text-edit?
578-
((TextEdit) (lsp--apply-text-edit text-edit?))
579-
((InsertReplaceEdit :insert :replace :new-text)
578+
((lsp-interface TextEdit) (lsp--apply-text-edit text-edit?))
579+
((lsp-interface InsertReplaceEdit :insert :replace :new-text)
580580
(lsp--apply-text-edit
581581
(lsp-make-text-edit
582582
:new-text new-text

lsp-mode.el

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5245,11 +5245,11 @@ identifier and the position respectively."
52455245
type Location, LocationLink, Location[] or LocationLink[]."
52465246
(setq locations
52475247
(pcase locations
5248-
((seq (or (Location)
5249-
(LocationLink)))
5248+
((seq (or (lsp-interface Location)
5249+
(lsp-interface LocationLink)))
52505250
(append locations nil))
5251-
((or (Location)
5252-
(LocationLink))
5251+
((or (lsp-interface Location)
5252+
(lsp-interface LocationLink))
52535253
(list locations))))
52545254

52555255
(cl-labels ((get-xrefs-in-file
@@ -5616,9 +5616,9 @@ When language is nil render as markup if `markdown-mode' is loaded."
56165616
(let ((inhibit-message t))
56175617
(or
56185618
(pcase content
5619-
((MarkedString :value :language)
5619+
((lsp-interface MarkedString :value :language)
56205620
(lsp--render-string value language))
5621-
((MarkupContent :value :kind)
5621+
((lsp-interface MarkupContent :value :kind)
56225622
(lsp--render-string value kind))
56235623
;; plain string
56245624
((pred stringp) (lsp--render-string content "markdown"))
@@ -6408,11 +6408,11 @@ perform the request synchronously."
64086408
(-mapcat
64096409
(-lambda (sym)
64106410
(pcase-exhaustive sym
6411-
((DocumentSymbol :name :children? :selection-range (Range :start))
6411+
((lsp-interface DocumentSymbol :name :children? :selection-range (lsp-interface Range :start))
64126412
(cons (cons (concat path name)
64136413
(lsp--position-to-point start))
64146414
(lsp--xref-elements-index children? (concat path name " / "))))
6415-
((SymbolInformation :name :location (Location :range (Range :start)))
6415+
((lsp-interface SymbolInformation :name :location (lsp-interface Location :range (lsp-interface Range :start)))
64166416
(list (cons (concat path name)
64176417
(lsp--position-to-point start))))))
64186418
symbols))

lsp-protocol.el

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Allowed params: %s" interface (reverse (-map #'cl-first params)))
129129
$$result))
130130
(-partition 2 plist))
131131
$$result)))
132-
`(pcase-defmacro ,interface (&rest property-bindings)
132+
`(cl-defun ,(intern (format "lsp--pcase-macroexpander-%s" interface)) (&rest property-bindings)
133133
,(if lsp-use-plists
134134
``(and
135135
(pred listp)
@@ -246,6 +246,25 @@ Allowed params: %s" interface (reverse (-map #'cl-first params)))
246246
(apply #'append)
247247
(cl-list* 'progn))))
248248

249+
(pcase-defmacro lsp-interface (interface &rest property-bindings)
250+
"If EXPVAL is an instance of INTERFACE, destructure it by matching its
251+
properties. EXPVAL should be a plist or hash table depending on the variable
252+
`lsp-use-plists'.
253+
254+
INTERFACE should be an LSP interface defined with `lsp-interface'. This form
255+
will not match if any of INTERFACE's required fields are missing in EXPVAL.
256+
257+
Each :PROPERTY keyword matches a field in EXPVAL. The keyword may be followed by
258+
an optional PATTERN, which is a `pcase' pattern to apply to the field's value.
259+
Otherwise, PROPERTY is let-bound to the field's value.
260+
261+
\(fn INTERFACE [:PROPERTY [PATTERN]]...)"
262+
(cl-check-type interface symbol)
263+
(let ((lsp-pcase-macroexpander
264+
(intern (format "lsp--pcase-macroexpander-%s" interface))))
265+
(cl-assert (fboundp lsp-pcase-macroexpander) "not a known LSP interface: %s" interface)
266+
(apply lsp-pcase-macroexpander property-bindings)))
267+
249268
(if lsp-use-plists
250269
(progn
251270
(defun lsp-get (from key)

test/lsp-protocol-test.el

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,37 +81,43 @@
8181
(lsp-make-my-position :line 30 :character 40 :camelCase nil)
8282
:specialProperty 42)))
8383
(should (pcase particular-range
84-
((MyRange :start (MyPosition :line start-line :character start-char :camel-case start-camelcase)
85-
:end (MyPosition :line end-line :character end-char :camel-case end-camelCase))
84+
((lsp-interface MyRange
85+
:start (lsp-interface MyPosition
86+
:line start-line :character start-char :camel-case start-camelcase)
87+
:end (lsp-interface MyPosition
88+
:line end-line :character end-char :camel-case end-camelCase))
8689
t)
8790
(_ nil)))
8891

8992
(should (pcase particular-extended-range
90-
((MyExtendedRange)
93+
((lsp-interface MyExtendedRange)
9194
t)
9295
(_ nil)))
9396

9497
;; a subclass can be matched by a pattern for a parent class
9598
(should (pcase particular-extended-range
96-
((MyRange :start (MyPosition :line start-line :character start-char :camel-case start-camelcase)
97-
:end (MyPosition :line end-line :character end-char :camel-case end-camelCase))
99+
((lsp-interface MyRange
100+
:start (lsp-interface MyPosition
101+
:line start-line :character start-char :camel-case start-camelcase)
102+
:end (lsp-interface MyPosition
103+
:line end-line :character end-char :camel-case end-camelCase))
98104
t)
99105
(_ nil)))
100106

101107
;; the new patterns should be able to be used with existing ones
102108
(should (pcase (list particular-range
103109
particular-extended-range)
104-
((seq (MyRange)
105-
(MyExtendedRange))
110+
((seq (lsp-interface MyRange)
111+
(lsp-interface MyExtendedRange))
106112
t)
107113
(_ nil)))
108114

109115
;; the existing seq pattern should detect that the ranges are
110116
;; not in the order specified by the inner patterns
111117
(should-not (pcase (list particular-range
112118
particular-extended-range)
113-
((seq (MyExtendedRange)
114-
(MyRange))
119+
((seq (lsp-interface MyExtendedRange)
120+
(lsp-interface MyRange))
115121
t)
116122
(_ nil)))
117123

@@ -122,40 +128,43 @@
122128
;; and the second instance is an equality check against the other
123129
;; :character value, which is different.
124130
(should-not (pcase particular-range
125-
((MyRange :start (MyPosition :line start-line :character :camel-case start-camelcase)
126-
:end (MyPosition :line end-line :character :camel-case end-camelCase))
131+
((lsp-interface MyRange
132+
:start (lsp-interface MyPosition
133+
:line start-line :character :camel-case start-camelcase)
134+
:end (lsp-interface MyPosition
135+
:line end-line :character :camel-case end-camelCase))
127136
t)
128137
(_ nil)))
129138

130139
;; if an optional property is requested when it does not exist, we
131140
;; should still match if the required stuff matches. Missing
132141
;; optional properties are bound to nil.
133142
(should (pcase particular-range
134-
((MyRange :start (MyPosition :optional?))
143+
((lsp-interface MyRange :start (lsp-interface MyPosition :optional?))
135144
(null optional?))
136145
(_ nil)))
137146

138147
;; we cannot request a key (whether or not it is optional) not in
139148
;; the interface, even if the expr-val has all the types specified
140149
;; by the interface. This is a programmer error.
141150
(should-error (pcase particular-range
142-
((MyRange :something-unrelated)
151+
((lsp-interface MyRange :something-unrelated)
143152
t)
144153
(_ nil)))
145154

146155
;; we do not use camelCase at this stage. This is a programmer error.
147156
(should-error (pcase particular-range
148-
((MyRange :start (MyPosition :camelCase))
157+
((lsp-interface MyRange :start (lsp-interface MyPosition :camelCase))
149158
t)
150159
(_ nil)))
151160
(should (pcase particular-range
152-
((MyRange :start (MyPosition :camel-case))
161+
((lsp-interface MyRange :start (lsp-interface MyPosition :camel-case))
153162
t)
154163
(_ nil)))
155164

156165
;; :end is missing, so we should fail to match the interface.
157166
(should-not (pcase (lsp-make-my-range :start (lsp-make-my-position :line 10 :character 20 :camelCase nil))
158-
((MyRange)
167+
((lsp-interface MyRange)
159168
t)
160169
(_ nil)))))
161170

0 commit comments

Comments
 (0)