Skip to content

Commit b76e803

Browse files
committed
Correctly indent where clauses
Revisit #82. Now rustfmt has a default style for `where` clauses, it makes sense to let rust-mode support it by default. How rust-mode currently indents `where` clauses is totally broken anyway. The line starting with `where` itself, the following lines that are part of the `where` clause, and the body were all indented incorrectly. This commit fixes this. **Note that this commit does not prescribe a certain indentation style for where clauses, but supports the most common ones, including rustfmt's default.** By choosing the location of (1) `where` and (2) the type parameter bounds, the user can follow three major indentation styles. There is no need for a configuration option to choose the style, as the locations of 1 and 2 dictate the style to follow. So all three styles can be used together in the same file. For each major style, the opening brace can be on the last line of the type parameter bounds or on a new line (a or b in the examples). All 6 styles are supported without having to change any configuration option. See the examples below and the new test cases. There is one more style that is unfortunately incompatible with the default `where` indentation style of rustfmt: when the user does not want `where` to be indented, but aligned with the `fn` or `trait` on the line before (see examples `foo4a` and `foo4b` below). To enable this style, the user has to set the new option `rust-indent-where-clause` to nil (it defaults to t). Putting `where` and the type parameter bounds on the same line as the function header is still supported of course (see examples `fooa` and `foob` below). As there is no indentation, this commit does not influence this. Note that rust-mode's indentation has a different goal than rustfmt. rustfmt reflows code, adds or removes line breaks, reindents, etc. rust-mode's indentation only indents lines. The goal is not to imitate rustfmt, but after running rustfmt on a file, reindenting it with rust-mode should be idempotent. This way, users do not have to deal with the eternal battle of differing indentation styles of rustfmt and rust-mode. The supported styles: ```rust fn foo1a(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } fn foo1b(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } ``` ```rust fn foo2a(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } fn foo2b(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } ``` ```rust fn foo3a(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } fn foo3b(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } ``` If the user wants `foo4` instead of `foo1`, `rust-indent-where-clause` must be set to nil. `foo2` and `foo3` are not influenced by this. ```rust fn foo4a(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } fn foo4a(a: A, b: B) -> C where A: Clone + Default, B: Eq, C: PartialEq { let body; } ``` Unchanged: ```rust fn fooa(a: A, b: B) -> C where A: Clone + Default, ... { let body; } fn foob(a: A, b: B) -> C where A: Clone + Default, ... { let body; } ```
1 parent 0601540 commit b76e803

File tree

2 files changed

+232
-1
lines changed

2 files changed

+232
-1
lines changed

rust-mode-tests.el

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,166 @@ fn foo4(a:int,
443443
}
444444
"))
445445

446+
(ert-deftest indent-body-after-where ()
447+
(test-indent
448+
"
449+
fn foo1(a: A, b: B) -> A
450+
where A: Clone + Default, B: Eq {
451+
let body;
452+
Foo {
453+
bar: 3
454+
}
455+
}
456+
457+
fn foo2(a: A, b: B) -> A
458+
where A: Clone + Default, B: Eq
459+
{
460+
let body;
461+
Foo {
462+
bar: 3
463+
}
464+
}
465+
"))
466+
467+
(ert-deftest indent-align-where-clauses-style1a ()
468+
(test-indent
469+
"
470+
fn foo1a(a: A, b: B, c: C) -> D
471+
where A: Clone + Default,
472+
B: Eq,
473+
C: PartialEq,
474+
D: PartialEq {
475+
let body;
476+
Foo {
477+
bar: 3
478+
}
479+
}
480+
"))
481+
482+
(ert-deftest indent-align-where-clauses-style1b ()
483+
(test-indent
484+
"
485+
fn foo1b(a: A, b: B, c: C) -> D
486+
where A: Clone + Default,
487+
B: Eq,
488+
C: PartialEq,
489+
D: PartialEq
490+
{
491+
let body;
492+
Foo {
493+
bar: 3
494+
}
495+
}
496+
"))
497+
498+
(ert-deftest indent-align-where-clauses-style2a ()
499+
(test-indent
500+
"
501+
fn foo2a(a: A, b: B, c: C) -> D where A: Clone + Default,
502+
B: Eq,
503+
C: PartialEq,
504+
D: PartialEq {
505+
let body;
506+
Foo {
507+
bar: 3
508+
}
509+
}
510+
"))
511+
512+
(ert-deftest indent-align-where-clauses-style2b ()
513+
(test-indent
514+
"
515+
fn foo2b(a: A, b: B, c: C) -> D where A: Clone + Default,
516+
B: Eq,
517+
C: PartialEq,
518+
D: PartialEq
519+
{
520+
let body;
521+
Foo {
522+
bar: 3
523+
}
524+
}
525+
"))
526+
527+
(ert-deftest indent-align-where-clauses-style3a ()
528+
(test-indent
529+
"
530+
fn foo3a(a: A, b: B, c: C) -> D where
531+
A: Clone + Default,
532+
B: Eq,
533+
C: PartialEq,
534+
D: PartialEq {
535+
let body;
536+
Foo {
537+
bar: 3
538+
}
539+
}
540+
"))
541+
542+
(ert-deftest indent-align-where-clauses-style3b ()
543+
(test-indent
544+
"
545+
fn foo3b(a: A, b: B, c: C) -> D where
546+
A: Clone + Default,
547+
B: Eq,
548+
C: PartialEq,
549+
D: PartialEq
550+
{
551+
let body;
552+
Foo {
553+
bar: 3
554+
}
555+
}
556+
"))
557+
558+
(ert-deftest indent-align-where-clauses-style4a ()
559+
(let ((rust-indent-where-clause nil))
560+
(test-indent
561+
"
562+
fn foo4a(a: A, b: B, c: C) -> D
563+
where A: Clone + Default,
564+
B: Eq,
565+
C: PartialEq,
566+
D: PartialEq {
567+
let body;
568+
Foo {
569+
bar: 3
570+
}
571+
}
572+
")))
573+
574+
(ert-deftest indent-align-where-clauses-style4b ()
575+
(let ((rust-indent-where-clause nil))
576+
(test-indent
577+
"
578+
fn foo4b(a: A, b: B, c: C) -> D
579+
where A: Clone + Default,
580+
B: Eq,
581+
C: PartialEq,
582+
D: PartialEq
583+
{
584+
let body;
585+
Foo {
586+
bar: 3
587+
}
588+
}
589+
")))
590+
591+
(ert-deftest indent-align-where-clauses-impl-example ()
592+
(test-indent
593+
"
594+
impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap<K, V, S>
595+
where K: Eq + Hash + Borrow<Q>,
596+
Q: Eq + Hash,
597+
S: HashState,
598+
{
599+
let body;
600+
Foo {
601+
bar: 3
602+
}
603+
}
604+
"))
605+
446606
(ert-deftest indent-square-bracket-alignment ()
447607
(test-indent
448608
"

rust-mode.el

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@
157157
:group 'rust-mode
158158
:safe #'booleanp)
159159

160+
(defcustom rust-indent-where-clause t
161+
"Indent the line starting with the where keyword following a
162+
function or trait. When nil, where will be aligned with fn or trait."
163+
:type 'boolean
164+
:group 'rust-mode
165+
:safe #'booleanp)
166+
160167
(defcustom rust-playpen-url-format "https://play.rust-lang.org/?code=%s"
161168
"Format string to use when submitting code to the playpen"
162169
:type 'string
@@ -213,7 +220,25 @@
213220
(back-to-indentation))
214221
(while (> (rust-paren-level) current-level)
215222
(backward-up-list)
216-
(back-to-indentation))))
223+
(back-to-indentation))
224+
;; When we're in the where clause, skip over it. First find out the start
225+
;; of the function and its paren level.
226+
(let ((function-start nil) (function-level nil))
227+
(save-excursion
228+
(rust-beginning-of-defun)
229+
(back-to-indentation)
230+
;; Avoid using multiple-value-bind
231+
(setq function-start (point)
232+
function-level (rust-paren-level)))
233+
;; On a where clause
234+
(when (or (looking-at "\\bwhere\\b")
235+
;; or in one of the following lines, e.g.
236+
;; where A: Eq
237+
;; B: Hash <- on this line
238+
(and (save-excursion
239+
(re-search-backward "\\bwhere\\b" function-start t))
240+
(= current-level function-level)))
241+
(goto-char function-start)))))
217242

218243
(defun rust-align-to-method-chain ()
219244
(save-excursion
@@ -348,6 +373,11 @@
348373
((and (nth 4 (syntax-ppss)) (looking-at "*"))
349374
(+ 1 baseline))
350375

376+
;; When the user chose not to indent the start of the where
377+
;; clause, put it on the baseline.
378+
((and (not rust-indent-where-clause) (looking-at "\\bwhere\\b"))
379+
baseline)
380+
351381
;; If we're in any other token-tree / sexp, then:
352382
(t
353383
(or
@@ -361,6 +391,47 @@
361391
;; Point is now at the beginning of the containing set of braces
362392
(rust-align-to-expr-after-brace)))
363393

394+
;; When where-clauses are spread over multiple lines, clauses
395+
;; should be aligned on the type parameters. In this case we
396+
;; take care of the second and following clauses (the ones
397+
;; that don't start with "where ")
398+
(save-excursion
399+
;; Find the start of the function, we'll use this to limit
400+
;; our search for "where ".
401+
(let ((function-start nil) (function-level nil))
402+
(save-excursion
403+
(rust-beginning-of-defun)
404+
(back-to-indentation)
405+
;; Avoid using multiple-value-bind
406+
(setq function-start (point)
407+
function-level (rust-paren-level)))
408+
;; When we're not on a line starting with "where ", but
409+
;; still on a where-clause line, go to "where "
410+
(when (and
411+
(not (looking-at "\\bwhere\\b"))
412+
;; We're looking at something like "F: ..."
413+
(and (looking-at (concat rust-re-ident ":"))
414+
;; There is a "where " somewhere after the
415+
;; start of the function.
416+
(re-search-backward "\\bwhere\\b"
417+
function-start t)
418+
;; Make sure we're not inside the function
419+
;; already (e.g. initializing a struct) by
420+
;; checking we are the same level.
421+
(= function-level level)))
422+
;; skip over "where"
423+
(forward-char 5)
424+
;; Unless "where" is at the end of the line
425+
(if (eolp)
426+
;; in this case the type parameters bounds are just
427+
;; indented once
428+
(+ baseline rust-indent-offset)
429+
;; otherwise, skip over whitespace,
430+
(skip-chars-forward "[:space:]")
431+
;; get the column of the type parameter and use that
432+
;; as indentation offset
433+
(current-column)))))
434+
364435
(progn
365436
(back-to-indentation)
366437
;; Point is now at the beginning of the current line

0 commit comments

Comments
 (0)