Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upstream: Re-implement rust-in-macro for performance #108

Merged
merged 1 commit into from
May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 84 additions & 12 deletions rustic.el
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,71 @@ symbols."
(let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point))))
(looking-back rustic-re-ident beg-of-symbol)))

(defvar-local rustic-macro-scopes nil
"Cache for the scopes calculated by `rustic-macro-scope'.
This variable can be `let' bound directly or indirectly around
`rustic-macro-scope' as an optimization but should not be otherwise
set.")

(defun rustic-macro-scope (start end)
"Return the scope of macros in the buffer.
The return value is a list of (START END) positions in the
buffer.
If set START and END are optimizations which limit the return
value to scopes which are approximately with this range."
(save-excursion
;; need to special case macro_rules which has unique syntax
(let ((scope nil)
(start (or start (point-min)))
(end (or end (point-max))))
(goto-char start)
;; if there is a start move back to the previous top level,
;; as any macros before that must have closed by this time.
(let ((top (syntax-ppss-toplevel-pos (syntax-ppss))))
(when top
(goto-char top)))
(while
(and
;; The movement below may have moved us passed end, in
;; which case search-forward will error
(< (point) end)
(search-forward "!" end t))
(let ((pt (point)))
(cond
;; in a string or comment is boring, move straight on
((rustic-in-str-or-cmnt))
;; in a normal macro,
((and (skip-chars-forward " \t\n\r")
(memq (char-after)
'(?\[ ?\( ?\{))
;; Check that we have a macro declaration after.
(rustic-looking-back-macro))
(let ((start (point)))
(ignore-errors (forward-list))
(setq scope (cons (list start (point)) scope))))
;; macro_rules, why, why, why did you not use macro syntax??
((save-excursion
;; yuck -- last test moves point, even if it fails
(goto-char (- pt 1))
(skip-chars-backward " \t\n\r")
(rustic-looking-back-str "macro_rules"))
(save-excursion
(when (re-search-forward "[[({]" nil t)
(backward-char)
(let ((start (point)))
(ignore-errors (forward-list))
(setq scope (cons (list start (point)) scope)))))))))
;; Return 'empty rather than nil, to indicate a buffer with no
;; macros at all.
(or scope 'empty))))

(defun rustic-looking-back-macro ()
"Non-nil if looking back at an ident followed by a !"
"Non-nil if looking back at an ident followed by a !
This is stricter than rust syntax which allows a space between
the ident and the ! symbol. If this space is allowed, then we
would also need a keyword check to avoid `if !(condition)` being
seen as a macro."
(if (> (- (point) (point-min)) 1)
(save-excursion
(backward-char)
Expand All @@ -478,18 +541,27 @@ symbols."
;; Rewind until the point no longer moves
(setq continue (/= starting (point)))))))

(defun rustic-in-macro ()
(save-excursion
(when (> (rustic-paren-level) 0)
(backward-up-list)
(rustic-rewind-irrelevant)
(or (rustic-looking-back-macro)
(and (rustic-looking-back-ident)
(save-excursion
(backward-sexp)
(rustic-rewind-irrelevant)
(rustic-looking-back-str "macro_rules!")))
(rustic-in-macro)))))
(defun rustic-in-macro (&optional start end)
"Return non-nil when point is within the scope of a macro.
If START and END are set, minimize the buffer analysis to
approximately this location as an optimization.
Alternatively, if `rustic-macro-scopes' is a list use the scope
information in this variable. This last is an optimization and
the caller is responsible for ensuring that the data in
`rustic-macro-scopes' is up to date."
(when (> (rustic-paren-level) 0)
(let ((scopes
(or
rustic-macro-scopes
(rustic-macro-scope start end))))
;; `rustic-macro-scope' can return the symbol `empty' if the
;; buffer has no macros at all.
(when (listp scopes)
(seq-some
(lambda (sc)
(and (>= (point) (car sc))
(< (point) (cadr sc))))
scopes)))))

(defun rustic-looking-at-where ()
"Return T when looking at the \"where\" keyword."
Expand Down
104 changes: 104 additions & 0 deletions test/rustic-rust-mode-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -1584,3 +1584,107 @@ fn imagine_long_enough_to_wrap_at_arrow(a:i32, b:char)
let body;
}
")))

(ert-deftest rustic-test-paren-matching-no-angle-brackets-in-macro-rules ()
(rustic-test-matching-parens
"
fn foo<A>(a:A) {
macro_rules! foo ( foo::<ignore the bracets> );
macro_rules! bar [ foo as Option<B> ];
}
macro_c!{
struct Boo<D> {}
}"
'((8 10))
;; Inside macros, it should not find any angle brackets, even if it normally
;; would
'(47 ;; foo <
62 ;; foo >
107 ;; bar <
109 ;; bar >
141 ;; macro_c <
143 ;; macro_c >
)))

(ert-deftest rustic-test-in-macro-do-not-fail-on-unbalance ()
(should
;; We don't care about the results here, so long as they do not error
(with-temp-buffer
(insert
"fn foo<A>(a:A) {
macro_c!{
struct Boo<D> {}
")
(rustic-mode)
(goto-char (point-max))
(syntax-ppss))))


(ert-deftest rustic-test-in-macro-no-caching ()
(should-not
(with-temp-buffer
(insert
"fn foo<A>(a:A) {
macro_c!{
struct Boo<D> {}
")
(rustic-mode)
(search-backward "macro")
;; do not use the cache
(let ((rustic-macro-scopes nil))
(rustic-in-macro)))))

(ert-deftest rustic-test-in-macro-fake-cache ()
(should
(with-temp-buffer
(insert
"fn foo<A>(a:A) {
macro_c!{
struct Boo<D> {}
")
(rustic-mode)
(search-backward "macro")
;; make the cache lie to make the whole buffer in scope
;; we need to be at paren level 1 for this to work
(let ((rustic-macro-scopes `((,(point-min) ,(point-max)))))
(rustic-in-macro)))))

(ert-deftest rustic-test-in-macro-broken-cache ()
(should-error
(with-temp-buffer
(insert
"fn foo<A>(a:A) {
macro_c!{
struct Boo<D> {}
")
(rustic-mode)
(search-backward "Boo")
;; do we use the cache at all
(let ((rustic-macro-scopes '(I should break)))
(rustic-in-macro)))))

(ert-deftest rustic-test-in-macro-nested ()
(should
(equal
(with-temp-buffer
(insert
"macro_rules! outer {
() => { vec![] };
}")
(rustic-mode)
(rustic-macro-scope (point-min) (point-max)))
'((38 40) (20 45)))))

(ert-deftest rustic-test-in-macro-not-with-space ()
(should
(equal
(with-temp-buffer
(insert
"fn foo<T>() {
if !(mem::size_of::<T>() > 8) {
bar()
}
}")
(rustic-mode)
(rustic-macro-scope (point-min) (point-max)))
'empty)))