Skip to content

Commit 52b632d

Browse files
committed
upstream: Re-implement rust-in-macro for performance
thanks to phillord
1 parent cb2bfde commit 52b632d

File tree

2 files changed

+188
-12
lines changed

2 files changed

+188
-12
lines changed

rustic.el

+84-12
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,71 @@ symbols."
453453
(let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point))))
454454
(looking-back rustic-re-ident beg-of-symbol)))
455455

456+
(defvar-local rustic-macro-scopes nil
457+
"Cache for the scopes calculated by `rustic-macro-scope'.
458+
This variable can be `let' bound directly or indirectly around
459+
`rustic-macro-scope' as an optimization but should not be otherwise
460+
set.")
461+
462+
(defun rustic-macro-scope (start end)
463+
"Return the scope of macros in the buffer.
464+
The return value is a list of (START END) positions in the
465+
buffer.
466+
If set START and END are optimizations which limit the return
467+
value to scopes which are approximately with this range."
468+
(save-excursion
469+
;; need to special case macro_rules which has unique syntax
470+
(let ((scope nil)
471+
(start (or start (point-min)))
472+
(end (or end (point-max))))
473+
(goto-char start)
474+
;; if there is a start move back to the previous top level,
475+
;; as any macros before that must have closed by this time.
476+
(let ((top (syntax-ppss-toplevel-pos (syntax-ppss))))
477+
(when top
478+
(goto-char top)))
479+
(while
480+
(and
481+
;; The movement below may have moved us passed end, in
482+
;; which case search-forward will error
483+
(< (point) end)
484+
(search-forward "!" end t))
485+
(let ((pt (point)))
486+
(cond
487+
;; in a string or comment is boring, move straight on
488+
((rustic-in-str-or-cmnt))
489+
;; in a normal macro,
490+
((and (skip-chars-forward " \t\n\r")
491+
(memq (char-after)
492+
'(?\[ ?\( ?\{))
493+
;; Check that we have a macro declaration after.
494+
(rustic-looking-back-macro))
495+
(let ((start (point)))
496+
(ignore-errors (forward-list))
497+
(setq scope (cons (list start (point)) scope))))
498+
;; macro_rules, why, why, why did you not use macro syntax??
499+
((save-excursion
500+
;; yuck -- last test moves point, even if it fails
501+
(goto-char (- pt 1))
502+
(skip-chars-backward " \t\n\r")
503+
(rustic-looking-back-str "macro_rules"))
504+
(save-excursion
505+
(when (re-search-forward "[[({]" nil t)
506+
(backward-char)
507+
(let ((start (point)))
508+
(ignore-errors (forward-list))
509+
(setq scope (cons (list start (point)) scope)))))))))
510+
;; Return 'empty rather than nil, to indicate a buffer with no
511+
;; macros at all.
512+
(or scope 'empty))))
513+
456514
(defun rustic-looking-back-macro ()
457515
"Non-nil if looking back at an ident followed by a !"
516+
"Non-nil if looking back at an ident followed by a !
517+
This is stricter than rust syntax which allows a space between
518+
the ident and the ! symbol. If this space is allowed, then we
519+
would also need a keyword check to avoid `if !(condition)` being
520+
seen as a macro."
458521
(if (> (- (point) (point-min)) 1)
459522
(save-excursion
460523
(backward-char)
@@ -478,18 +541,27 @@ symbols."
478541
;; Rewind until the point no longer moves
479542
(setq continue (/= starting (point)))))))
480543

481-
(defun rustic-in-macro ()
482-
(save-excursion
483-
(when (> (rustic-paren-level) 0)
484-
(backward-up-list)
485-
(rustic-rewind-irrelevant)
486-
(or (rustic-looking-back-macro)
487-
(and (rustic-looking-back-ident)
488-
(save-excursion
489-
(backward-sexp)
490-
(rustic-rewind-irrelevant)
491-
(rustic-looking-back-str "macro_rules!")))
492-
(rustic-in-macro)))))
544+
(defun rustic-in-macro (&optional start end)
545+
"Return non-nil when point is within the scope of a macro.
546+
If START and END are set, minimize the buffer analysis to
547+
approximately this location as an optimization.
548+
Alternatively, if `rustic-macro-scopes' is a list use the scope
549+
information in this variable. This last is an optimization and
550+
the caller is responsible for ensuring that the data in
551+
`rustic-macro-scopes' is up to date."
552+
(when (> (rustic-paren-level) 0)
553+
(let ((scopes
554+
(or
555+
rustic-macro-scopes
556+
(rustic-macro-scope start end))))
557+
;; `rustic-macro-scope' can return the symbol `empty' if the
558+
;; buffer has no macros at all.
559+
(when (listp scopes)
560+
(seq-some
561+
(lambda (sc)
562+
(and (>= (point) (car sc))
563+
(< (point) (cadr sc))))
564+
scopes)))))
493565

494566
(defun rustic-looking-at-where ()
495567
"Return T when looking at the \"where\" keyword."

test/rustic-rust-mode-test.el

+104
Original file line numberDiff line numberDiff line change
@@ -1584,3 +1584,107 @@ fn imagine_long_enough_to_wrap_at_arrow(a:i32, b:char)
15841584
let body;
15851585
}
15861586
")))
1587+
1588+
(ert-deftest rustic-test-paren-matching-no-angle-brackets-in-macro-rules ()
1589+
(rustic-test-matching-parens
1590+
"
1591+
fn foo<A>(a:A) {
1592+
macro_rules! foo ( foo::<ignore the bracets> );
1593+
macro_rules! bar [ foo as Option<B> ];
1594+
}
1595+
macro_c!{
1596+
struct Boo<D> {}
1597+
}"
1598+
'((8 10))
1599+
;; Inside macros, it should not find any angle brackets, even if it normally
1600+
;; would
1601+
'(47 ;; foo <
1602+
62 ;; foo >
1603+
107 ;; bar <
1604+
109 ;; bar >
1605+
141 ;; macro_c <
1606+
143 ;; macro_c >
1607+
)))
1608+
1609+
(ert-deftest rustic-test-in-macro-do-not-fail-on-unbalance ()
1610+
(should
1611+
;; We don't care about the results here, so long as they do not error
1612+
(with-temp-buffer
1613+
(insert
1614+
"fn foo<A>(a:A) {
1615+
macro_c!{
1616+
struct Boo<D> {}
1617+
")
1618+
(rustic-mode)
1619+
(goto-char (point-max))
1620+
(syntax-ppss))))
1621+
1622+
1623+
(ert-deftest rustic-test-in-macro-no-caching ()
1624+
(should-not
1625+
(with-temp-buffer
1626+
(insert
1627+
"fn foo<A>(a:A) {
1628+
macro_c!{
1629+
struct Boo<D> {}
1630+
")
1631+
(rustic-mode)
1632+
(search-backward "macro")
1633+
;; do not use the cache
1634+
(let ((rustic-macro-scopes nil))
1635+
(rustic-in-macro)))))
1636+
1637+
(ert-deftest rustic-test-in-macro-fake-cache ()
1638+
(should
1639+
(with-temp-buffer
1640+
(insert
1641+
"fn foo<A>(a:A) {
1642+
macro_c!{
1643+
struct Boo<D> {}
1644+
")
1645+
(rustic-mode)
1646+
(search-backward "macro")
1647+
;; make the cache lie to make the whole buffer in scope
1648+
;; we need to be at paren level 1 for this to work
1649+
(let ((rustic-macro-scopes `((,(point-min) ,(point-max)))))
1650+
(rustic-in-macro)))))
1651+
1652+
(ert-deftest rustic-test-in-macro-broken-cache ()
1653+
(should-error
1654+
(with-temp-buffer
1655+
(insert
1656+
"fn foo<A>(a:A) {
1657+
macro_c!{
1658+
struct Boo<D> {}
1659+
")
1660+
(rustic-mode)
1661+
(search-backward "Boo")
1662+
;; do we use the cache at all
1663+
(let ((rustic-macro-scopes '(I should break)))
1664+
(rustic-in-macro)))))
1665+
1666+
(ert-deftest rustic-test-in-macro-nested ()
1667+
(should
1668+
(equal
1669+
(with-temp-buffer
1670+
(insert
1671+
"macro_rules! outer {
1672+
() => { vec![] };
1673+
}")
1674+
(rustic-mode)
1675+
(rustic-macro-scope (point-min) (point-max)))
1676+
'((38 40) (20 45)))))
1677+
1678+
(ert-deftest rustic-test-in-macro-not-with-space ()
1679+
(should
1680+
(equal
1681+
(with-temp-buffer
1682+
(insert
1683+
"fn foo<T>() {
1684+
if !(mem::size_of::<T>() > 8) {
1685+
bar()
1686+
}
1687+
}")
1688+
(rustic-mode)
1689+
(rustic-macro-scope (point-min) (point-max)))
1690+
'empty)))

0 commit comments

Comments
 (0)