Skip to content

Commit bfe4056

Browse files
phillordmookid
authored andcommitted
Re-implement rust-in-macro for performance
rust-in-macro could cause significant performance problems resulting in a very choppy user experience. Reimplement rust-in macro in a somewhat simpler manner and in way which allows both allows restriction to parts of the buffer and caching of buffer analysis. Optimize rust-syntax-propertize to use this caching mechanism. Fixes #208 Fixes #288
1 parent eca55c0 commit bfe4056

File tree

2 files changed

+225
-40
lines changed

2 files changed

+225
-40
lines changed

rust-mode-tests.el

+108-2
Original file line numberDiff line numberDiff line change
@@ -2139,8 +2139,8 @@ fn main() {
21392139
(should (equal (scan-sexps (+ 1 close-pos) -1) open-pos))))
21402140
(dolist (nonpar-pos nonparen-positions)
21412141
(let ((nonpar-syntax-class (syntax-class (syntax-after nonpar-pos))))
2142-
(should (not (equal 4 nonpar-syntax-class)))
2143-
(should (not (equal 5 nonpar-syntax-class)))))))
2142+
(should-not (equal 4 nonpar-syntax-class))
2143+
(should-not (equal 5 nonpar-syntax-class))))))
21442144

21452145
(ert-deftest rust-test-unmatched-single-quote-in-comment-paren-matching ()
21462146
;; This was a bug from the char quote handling that affected the paren
@@ -2923,6 +2923,112 @@ macro_c!{
29232923
125 ;; macro_d >
29242924
)))
29252925

2926+
(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macro-rules ()
2927+
(rust-test-matching-parens
2928+
"
2929+
fn foo<A>(a:A) {
2930+
macro_rules! foo ( foo::<ignore the bracets> );
2931+
macro_rules! bar [ foo as Option<B> ];
2932+
}
2933+
2934+
macro_c!{
2935+
struct Boo<D> {}
2936+
}"
2937+
'((8 10))
2938+
;; Inside macros, it should not find any angle brackets, even if it normally
2939+
;; would
2940+
'(47 ;; foo <
2941+
62 ;; foo >
2942+
107 ;; bar <
2943+
109 ;; bar >
2944+
141 ;; macro_c <
2945+
143 ;; macro_c >
2946+
)))
2947+
2948+
(ert-deftest rust-test-in-macro-do-not-fail-on-unbalance ()
2949+
(should
2950+
;; We don't care about the results here, so long as they do not error
2951+
(with-temp-buffer
2952+
(insert
2953+
"fn foo<A>(a:A) {
2954+
macro_c!{
2955+
struct Boo<D> {}
2956+
")
2957+
(rust-mode)
2958+
(goto-char (point-max))
2959+
(syntax-ppss))))
2960+
2961+
2962+
(ert-deftest rust-test-in-macro-no-caching ()
2963+
(should-not
2964+
(with-temp-buffer
2965+
(insert
2966+
"fn foo<A>(a:A) {
2967+
macro_c!{
2968+
struct Boo<D> {}
2969+
")
2970+
(rust-mode)
2971+
(search-backward "macro")
2972+
;; do not use the cache
2973+
(let ((rust-macro-scopes nil))
2974+
(rust-in-macro)))))
2975+
2976+
(ert-deftest rust-test-in-macro-fake-cache ()
2977+
(should
2978+
(with-temp-buffer
2979+
(insert
2980+
"fn foo<A>(a:A) {
2981+
macro_c!{
2982+
struct Boo<D> {}
2983+
")
2984+
(rust-mode)
2985+
(search-backward "macro")
2986+
;; make the cache lie to make the whole buffer in scope
2987+
;; we need to be at paren level 1 for this to work
2988+
(let ((rust-macro-scopes `((,(point-min) ,(point-max)))))
2989+
(rust-in-macro)))))
2990+
2991+
(ert-deftest rust-test-in-macro-broken-cache ()
2992+
(should-error
2993+
(with-temp-buffer
2994+
(insert
2995+
"fn foo<A>(a:A) {
2996+
macro_c!{
2997+
struct Boo<D> {}
2998+
")
2999+
(rust-mode)
3000+
(search-backward "Boo")
3001+
;; do we use the cache at all
3002+
(let ((rust-macro-scopes '(I should break)))
3003+
(rust-in-macro)))))
3004+
3005+
(ert-deftest rust-test-in-macro-nested ()
3006+
(should
3007+
(equal
3008+
(with-temp-buffer
3009+
(insert
3010+
"macro_rules! outer {
3011+
() => { vec![] };
3012+
}")
3013+
(rust-mode)
3014+
(rust-macro-scope (point-min) (point-max)))
3015+
'((38 40) (20 45)))))
3016+
3017+
(ert-deftest rust-test-in-macro-not-with-space ()
3018+
(should
3019+
(equal
3020+
(with-temp-buffer
3021+
(insert
3022+
"fn foo<T>() {
3023+
if !(mem::size_of::<T>() > 8) {
3024+
bar()
3025+
}
3026+
}")
3027+
(rust-mode)
3028+
(rust-macro-scope (point-min) (point-max)))
3029+
'empty)))
3030+
3031+
29263032
(ert-deftest rust-test-paren-matching-type-with-module-name ()
29273033
(rust-test-matching-parens
29283034
"

rust-mode.el

+117-38
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,12 @@ symbols."
106106
(looking-back rust-re-ident beg-of-symbol)))
107107

108108
(defun rust-looking-back-macro ()
109-
"Non-nil if looking back at an ident followed by a !"
109+
"Non-nil if looking back at an ident followed by a !
110+
111+
This is stricter than rust syntax which allows a space between
112+
the ident and the ! symbol. If this space is allowed, then we
113+
would also need a keyword check to avoid `if !(condition)` being
114+
seen as a macro."
110115
(if (> (- (point) (point-min)) 1)
111116
(save-excursion
112117
(backward-char)
@@ -260,18 +265,90 @@ to the function arguments. When nil, `->' will be indented one level."
260265
;; Rewind until the point no longer moves
261266
(setq continue (/= starting (point)))))))
262267

263-
(defun rust-in-macro ()
268+
(defvar-local rust-macro-scopes nil
269+
"Cache for the scopes calculated by `rust-macro-scope'.
270+
271+
This variable can be `let' bound directly or indirectly around
272+
`rust-macro-scope' as an optimization but should not be otherwise
273+
set.")
274+
275+
(defun rust-macro-scope (start end)
276+
"Return the scope of macros in the buffer.
277+
278+
The return value is a list of (START END) positions in the
279+
buffer.
280+
281+
If set START and END are optimizations which limit the return
282+
value to scopes which are approximately with this range."
264283
(save-excursion
265-
(when (> (rust-paren-level) 0)
266-
(backward-up-list)
267-
(rust-rewind-irrelevant)
268-
(or (rust-looking-back-macro)
269-
(and (rust-looking-back-ident)
270-
(save-excursion
271-
(backward-sexp)
272-
(rust-rewind-irrelevant)
273-
(rust-looking-back-str "macro_rules!")))
274-
(rust-in-macro)))))
284+
;; need to special case macro_rules which has unique syntax
285+
(let ((scope nil)
286+
(start (or start (point-min)))
287+
(end (or end (point-max))))
288+
(goto-char start)
289+
;; if there is a start move back to the previous top level,
290+
;; as any macros before that must have closed by this time.
291+
(let ((top (syntax-ppss-toplevel-pos (syntax-ppss))))
292+
(when top
293+
(goto-char top)))
294+
(while
295+
(and
296+
;; The movement below may have moved us passed end, in
297+
;; which case search-forward will error
298+
(< (point) end)
299+
(search-forward "!" end t))
300+
(let ((pt (point)))
301+
(cond
302+
;; in a string or comment is boring, move straight on
303+
((rust-in-str-or-cmnt))
304+
;; in a normal macro,
305+
((and (skip-chars-forward " \t\n\r")
306+
(memq (char-after)
307+
'(?\[ ?\( ?\{))
308+
;; Check that we have a macro declaration after.
309+
(rust-looking-back-macro))
310+
(let ((start (point)))
311+
(ignore-errors (forward-list))
312+
(setq scope (cons (list start (point)) scope))))
313+
;; macro_rules, why, why, why did you not use macro syntax??
314+
((save-excursion
315+
;; yuck -- last test moves point, even if it fails
316+
(goto-char (- pt 1))
317+
(skip-chars-backward " \t\n\r")
318+
(rust-looking-back-str "macro_rules"))
319+
(save-excursion
320+
(when (re-search-forward "[[({]" nil t)
321+
(backward-char)
322+
(let ((start (point)))
323+
(ignore-errors (forward-list))
324+
(setq scope (cons (list start (point)) scope)))))))))
325+
;; Return 'empty rather than nil, to indicate a buffer with no
326+
;; macros at all.
327+
(or scope 'empty))))
328+
329+
(defun rust-in-macro (&optional start end)
330+
"Return non-nil when point is within the scope of a macro.
331+
332+
If START and END are set, minimize the buffer analysis to
333+
approximately this location as an optimization.
334+
335+
Alternatively, if `rust-macro-scopes' is a list use the scope
336+
information in this variable. This last is an optimization and
337+
the caller is responsible for ensuring that the data in
338+
`rust-macro-scopes' is up to date."
339+
(when (> (rust-paren-level) 0)
340+
(let ((scopes
341+
(or
342+
rust-macro-scopes
343+
(rust-macro-scope start end))))
344+
;; `rust-macro-scope' can return the symbol `empty' if the
345+
;; buffer has no macros at all.
346+
(when (listp scopes)
347+
(seq-some
348+
(lambda (sc)
349+
(and (>= (point) (car sc))
350+
(< (point) (cadr sc))))
351+
scopes)))))
275352

276353
(defun rust-looking-at-where ()
277354
"Return T when looking at the \"where\" keyword."
@@ -1208,32 +1285,34 @@ whichever comes first."
12081285

12091286
(defun rust-syntax-propertize (start end)
12101287
"A `syntax-propertize-function' to apply properties from START to END."
1211-
(goto-char start)
1212-
(let ((str-start (rust-in-str-or-cmnt)))
1213-
(when str-start
1214-
(rust--syntax-propertize-raw-string str-start end)))
1215-
(funcall
1216-
(syntax-propertize-rules
1217-
;; Character literals.
1218-
(rust--char-literal-rx (1 "\"") (2 "\""))
1219-
;; Raw strings.
1220-
("\\(r\\)#*\""
1221-
(0 (ignore
1222-
(goto-char (match-end 0))
1223-
(unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0))))
1224-
(put-text-property (match-beginning 1) (match-end 1)
1225-
'syntax-table (string-to-syntax "|"))
1226-
(rust--syntax-propertize-raw-string (match-beginning 0) end)))))
1227-
("[<>]"
1228-
(0 (ignore
1229-
(when (save-match-data
1230-
(save-excursion
1231-
(goto-char (match-beginning 0))
1232-
(rust-ordinary-lt-gt-p)))
1233-
(put-text-property (match-beginning 0) (match-end 0)
1234-
'syntax-table (string-to-syntax "."))
1235-
(goto-char (match-end 0)))))))
1236-
(point) end))
1288+
;; Cache all macro scopes as an optimization. See issue #208
1289+
(let ((rust-macro-scopes (rust-macro-scope start end)))
1290+
(goto-char start)
1291+
(let ((str-start (rust-in-str-or-cmnt)))
1292+
(when str-start
1293+
(rust--syntax-propertize-raw-string str-start end)))
1294+
(funcall
1295+
(syntax-propertize-rules
1296+
;; Character literals.
1297+
(rust--char-literal-rx (1 "\"") (2 "\""))
1298+
;; Raw strings.
1299+
("\\(r\\)#*\""
1300+
(0 (ignore
1301+
(goto-char (match-end 0))
1302+
(unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0))))
1303+
(put-text-property (match-beginning 1) (match-end 1)
1304+
'syntax-table (string-to-syntax "|"))
1305+
(rust--syntax-propertize-raw-string (match-beginning 0) end)))))
1306+
("[<>]"
1307+
(0 (ignore
1308+
(when (save-match-data
1309+
(save-excursion
1310+
(goto-char (match-beginning 0))
1311+
(rust-ordinary-lt-gt-p)))
1312+
(put-text-property (match-beginning 0) (match-end 0)
1313+
'syntax-table (string-to-syntax "."))
1314+
(goto-char (match-end 0)))))))
1315+
(point) end)))
12371316

12381317
(defun rust-fill-prefix-for-comment-start (line-start)
12391318
"Determine what to use for `fill-prefix' based on the text at LINE-START."

0 commit comments

Comments
 (0)