[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/rust-mode 42ba58df6c 1/4: Simplify and correct angle brack
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/rust-mode 42ba58df6c 1/4: Simplify and correct angle bracket propertizing and macro argument detection. |
Date: |
Tue, 20 Dec 2022 15:00:32 -0500 (EST) |
branch: elpa/rust-mode
commit 42ba58df6c3071576f2ddc6e0a285db57b7e690b
Author: Jim Blandy <jimb@red-bean.com>
Commit: Jim Blandy <jimb@red-bean.com>
Simplify and correct angle bracket propertizing and macro argument
detection.
Fixes #465.
When `rust-syntax-propertize` uses `rust-macro-scopes` to find
ranges of text that are macro arguments, it ends up inadvertently
poisoning the `syntax-ppss` cache by applying it to text that doesn't
have the necessary `syntax-table` properties applied yet - the very
job that `rust-syntax-propertize` is trying to do.
However, `rust-macro-scopes` does much more work than necessary.
Rather than producing a list of ranges of macro arguments, we can just
use the list of enclosing opening parens provided by syntax-ppss,
checking each paren to see if it seems to be a macro or `macro_rules`
call.
We have to keep syntax-ppss's cache accurate for other reasons anyway,
so we might as well just use its data, rather than introducing another
cache of our own - especially a problematic one (see #465).
* rust-mode.el (rust-in-macro): Consult `syntax-ppss`'s list of enclosing
parens, rather than using `rust-macro-scope`. Remove optional arguments,
which
were only used by tests.
(rust-macro-scopes, rust-macro-scope): Delete. Now we just use
`syntax-ppss`'s
internal cache.
(rust-syntax-propertize): Don't bind `rust-macro-scopes`.
(rust-looking-back-macro-rules): New function.
(rust-looking-back-macro): Support a space between macro name and `!`, by
consulting `rust-expression-introducers`.
(rust-expression-introducers): New constant. Use in
`rust-looking-back-macro`
and `rust-is-in-expression-context`.
(rust-is-in-expression-context): Use `rust-expression-introducers`.
(rust-looking-back-ident): Don't use `looking-back`. We've already moved to
the
correct spot for `looking-at`, within a `save-excursion`.
* rust-mode-tests.el: Update tests.
---
rust-mode-tests.el | 72 ++++++-------------
rust-mode.el | 203 ++++++++++++++++++++---------------------------------
2 files changed, 99 insertions(+), 176 deletions(-)
diff --git a/rust-mode-tests.el b/rust-mode-tests.el
index e4949b26f7..abbfcb600c 100644
--- a/rust-mode-tests.el
+++ b/rust-mode-tests.el
@@ -3116,7 +3116,7 @@ macro_c!{
(syntax-ppss))))
-(ert-deftest rust-test-in-macro-no-caching ()
+(ert-deftest rust-test-in-macro-around-opening ()
(should-not
(with-temp-buffer
(insert
@@ -3125,66 +3125,38 @@ macro_c!{
struct Boo<D> {}
")
(rust-mode)
- (search-backward "macro")
- ;; do not use the cache
- (let ((rust-macro-scopes nil))
- (rust-in-macro)))))
-
-(ert-deftest rust-test-in-macro-fake-cache ()
- (should
- (with-temp-buffer
- (insert
- "fn foo<A>(a:A) {
- macro_c!{
- struct Boo<D> {}
-")
- (rust-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 ((rust-macro-scopes `((,(point-min) ,(point-max)))))
- (rust-in-macro)))))
-
-(ert-deftest rust-test-in-macro-broken-cache ()
- (should-error
- (with-temp-buffer
- (insert
- "fn foo<A>(a:A) {
- macro_c!{
- struct Boo<D> {}
-")
- (rust-mode)
- (search-backward "Boo")
- ;; do we use the cache at all
- (let ((rust-macro-scopes '(I should break)))
- (rust-in-macro)))))
+ (search-backward "macro_c")
+ (and
+ (not (rust-in-macro))
+ (progn (forward-thing 'symbol 1) (not (rust-in-macro)))
+ (progn (forward-char 1) (rust-in-macro))
+ (progn (goto-char (point-max)) (rust-in-macro))))))
(ert-deftest rust-test-in-macro-nested ()
- (should
- (equal
- (with-temp-buffer
- (insert
- "macro_rules! outer {
+ (with-temp-buffer
+ (insert
+ "macro_rules! outer {
() => { vec![] };
}")
- (rust-mode)
- (rust-macro-scope (point-min) (point-max)))
- '((38 40) (20 45)))))
+ (rust-mode)
+ (should (progn (goto-char 20) (not (rust-in-macro))))
+ (should (progn (goto-char 21) (eq (rust-in-macro) 20)))
+ (should (progn (goto-char 38) (eq (rust-in-macro) 20)))
+ (should (progn (goto-char 39) (eq (rust-in-macro) 38)))
+ (should (progn (goto-char 40) (eq (rust-in-macro) 20)))
+ (should (progn (goto-char 44) (eq (rust-in-macro) 20)))
+ (should (progn (goto-char 45) (not (rust-in-macro))))))
(ert-deftest rust-test-in-macro-not-with-space ()
- (should
- (equal
- (with-temp-buffer
- (insert
+ (with-temp-buffer
+ (insert
"fn foo<T>() {
if !(mem::size_of::<T>() > 8) {
bar()
}
}")
- (rust-mode)
- (rust-macro-scope (point-min) (point-max)))
- 'empty)))
-
+ (rust-mode)
+ (should (progn (goto-char 24) (not (rust-in-macro))))))
(ert-deftest rust-test-paren-matching-type-with-module-name ()
(rust-test-matching-parens
diff --git a/rust-mode.el b/rust-mode.el
index 80106b836a..780474f16f 100644
--- a/rust-mode.el
+++ b/rust-mode.el
@@ -362,6 +362,10 @@ See `prettify-symbols-compose-predicate'."
"bool"
"str" "char"))
+(defconst rust-expression-introducers
+ '("if" "while" "match" "return" "box" "in")
+ "List of Rust keywords that are always followed by expressions.")
+
(defconst rust-number-with-type
(eval-when-compile
(concat
@@ -526,22 +530,36 @@ symbols."
symbols)))))
(defun rust-looking-back-ident ()
- "Non-nil if we are looking backwards at a valid rust identifier."
- (let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point))))
- (looking-back rust-re-ident beg-of-symbol)))
+ "Non-nil if we are looking backwards at a valid rust identifier.
+If we are, regexp match 0 is the identifier."
+ (let ((outer-point (point)))
+ (save-excursion
+ (forward-thing 'symbol -1)
+ (and (looking-at rust-re-ident)
+ (eq (match-end 0) outer-point)))))
(defun rust-looking-back-macro ()
- "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)
- (and (= ?! (char-after))
- (rust-looking-back-ident)))))
+ "Non-nil if looking back at a potential macro name followed by a \"!\".
+If we are, regexp match 0 is the macro name."
+ (save-excursion
+ ;; Look past whitespace and line breaks.
+ ;; > is okay because we only use it for \n and \r, not "*/"
+ (skip-syntax-backward "->")
+ (when (eq (char-before) ?!)
+ (forward-char -1)
+ (skip-syntax-backward "->")
+ (when (rust-looking-back-ident)
+ (let ((ident (match-string 0)))
+ (not (member ident rust-expression-introducers)))))))
+
+(defun rust-looking-back-macro-rules ()
+ "Non-nil if looking back at \"macro_rules IDENT !\"."
+ (save-excursion
+ (skip-syntax-backward "->")
+ (let ((outer-point (point)))
+ (forward-thing 'symbol -2)
+ (and (looking-at (concat "macro_rules\\s-*!\\s-*" rust-re-ident))
+ (eq (match-end 0) outer-point)))))
;;; Syntax definitions and helpers
@@ -562,90 +580,25 @@ seen as a macro."
;; Rewind until the point no longer moves
(setq continue (/= starting (point)))))))
-(defvar-local rust-macro-scopes nil
- "Cache for the scopes calculated by `rust-macro-scope'.
-
-This variable can be `let' bound directly or indirectly around
-`rust-macro-scope' as an optimization but should not be otherwise
-set.")
-
-(defun rust-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
- ((rust-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.
- (rust-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")
- (rust-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 rust-in-macro (&optional start end)
+(defun rust-in-macro ()
"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 `rust-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
-`rust-macro-scopes' is up to date."
- (when (> (rust-paren-level) 0)
- (let ((scopes
- (or
- rust-macro-scopes
- (rust-macro-scope start end))))
- ;; `rust-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)))))
+If we are, return the position of the opening bracket of the macro's
arguments."
+ (let ((ppss (syntax-ppss)))
+ ;; If we're in a string or comment, we're definitely not on a token a macro
+ ;; will see.
+ (when (not (or (nth 3 ppss) (nth 4 ppss)))
+ ;; Walk outward to enclosing parens, looking for one preceded by "ident
!"
+ ;; or "macro_rules! ident".
+ (let (result
+ (enclosing (reverse (nth 9 ppss))))
+ (save-excursion
+ (while enclosing
+ (goto-char (car enclosing))
+ (if (or (rust-looking-back-macro)
+ (rust-looking-back-macro-rules))
+ (setq result (point) enclosing nil)
+ (setq enclosing (cdr enclosing)))))
+ result))))
(defun rust-looking-at-where ()
"Return T when looking at the \"where\" keyword."
@@ -1089,7 +1042,7 @@ outside of this context."
(cond
;; Certain keywords always introduce expressions
- ((rust-looking-back-symbols '("if" "while" "match" "return" "box"
"in")) t)
+ ((rust-looking-back-symbols rust-expression-introducers) t)
;; "as" introduces a type
((rust-looking-back-symbols '("as")) nil)
@@ -1411,34 +1364,32 @@ whichever comes first."
(defun rust-syntax-propertize (start end)
"A `syntax-propertize-function' to apply properties from START to END."
- ;; Cache all macro scopes as an optimization. See issue #208
- (let ((rust-macro-scopes (rust-macro-scope start end)))
- (goto-char start)
- (let ((str-start (rust-in-str-or-cmnt)))
- (when str-start
- (rust--syntax-propertize-raw-string str-start end)))
- (funcall
- (syntax-propertize-rules
- ;; Character literals.
- (rust--char-literal-rx (1 "\"") (2 "\""))
- ;; Raw strings.
- ("\\(r\\)#*\""
- (0 (ignore
- (goto-char (match-end 0))
- (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0))))
- (put-text-property (match-beginning 1) (match-end 1)
- 'syntax-table (string-to-syntax "|"))
- (rust--syntax-propertize-raw-string (match-beginning 0) end)))))
- ("[<>]"
- (0 (ignore
- (when (save-match-data
- (save-excursion
- (goto-char (match-beginning 0))
- (rust-ordinary-lt-gt-p)))
- (put-text-property (match-beginning 0) (match-end 0)
- 'syntax-table (string-to-syntax "."))
- (goto-char (match-end 0)))))))
- (point) end)))
+ (goto-char start)
+ (let ((str-start (rust-in-str-or-cmnt)))
+ (when str-start
+ (rust--syntax-propertize-raw-string str-start end)))
+ (funcall
+ (syntax-propertize-rules
+ ;; Character literals.
+ (rust--char-literal-rx (1 "\"") (2 "\""))
+ ;; Raw strings.
+ ("\\(r\\)#*\""
+ (0 (ignore
+ (goto-char (match-end 0))
+ (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0))))
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'syntax-table (string-to-syntax "|"))
+ (rust--syntax-propertize-raw-string (match-beginning 0) end)))))
+ ("[<>]"
+ (0 (ignore
+ (when (save-match-data
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (rust-ordinary-lt-gt-p)))
+ (put-text-property (match-beginning 0) (match-end 0)
+ 'syntax-table (string-to-syntax "."))
+ (goto-char (match-end 0)))))))
+ (point) end))
(defun rust-fill-prefix-for-comment-start (line-start)
"Determine what to use for `fill-prefix' based on the text at LINE-START."