branch: elpa/swift-mode
commit 25e41ed7d405ea566a45fef7cdb673b86ae01701
Author: taku0 <mxxouy6x3m_git...@tatapa.org>
Commit: taku0 <mxxouy6x3m_git...@tatapa.org>

    Support if-expression and switch expression
    
    
https://github.com/apple/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md
---
 swift-mode-beginning-of-defun.el          |   6 +-
 swift-mode-indent.el                      | 171 +++++++++++++++++++++----
 swift-mode-lexer.el                       |  14 +--
 test/swift-files/indent/expressions.swift | 202 ++++++++++++++++++++++++++++++
 test/swift-files/indent/identifiers.swift |   2 +-
 test/swift-files/indent/statements.swift  |  18 ++-
 6 files changed, 375 insertions(+), 38 deletions(-)

diff --git a/swift-mode-beginning-of-defun.el b/swift-mode-beginning-of-defun.el
index 079db0f7e7..0309055071 100644
--- a/swift-mode-beginning-of-defun.el
+++ b/swift-mode-beginning-of-defun.el
@@ -151,7 +151,7 @@ The cursor must be at the beginning of a statement."
        ((member (swift-mode:token:text token) '("var" "let"))
         (when (swift-mode:class-like-member-p) token))
        ((equal (swift-mode:token:text token) "case")
-        (swift-mode:backward-sexps-until-open-curly-brace)
+        (swift-mode:backward-sexps-until-open-curly-bracket)
         (swift-mode:beginning-of-statement)
         (let ((parent-token (swift-mode:find-defun-keyword-simple)))
           (when (equal (swift-mode:token:text parent-token) "enum")
@@ -201,7 +201,7 @@ The cursor must be at the beginning of a statement."
 Also return t if the cursor is on a global declaration.
 Return nil otherwise."
   (or
-   (let ((parent (swift-mode:backward-sexps-until-open-curly-brace)))
+   (let ((parent (swift-mode:backward-sexps-until-open-curly-bracket)))
      (eq (swift-mode:token:type parent) 'outside-of-buffer))
    (progn
      (swift-mode:beginning-of-statement)
@@ -1391,7 +1391,7 @@ of ancestors."
   (if (bobp)
       nil
     (let ((name-token (swift-mode:current-defun-name-token)))
-      (swift-mode:backward-sexps-until-open-curly-brace)
+      (swift-mode:backward-sexps-until-open-curly-bracket)
       (if name-token
           (cons name-token (swift-mode:current-defun-name-token-list))
         (swift-mode:current-defun-name-token-list)))))
diff --git a/swift-mode-indent.el b/swift-mode-indent.el
index fb81534442..9dc259d322 100644
--- a/swift-mode-indent.el
+++ b/swift-mode-indent.el
@@ -308,7 +308,7 @@ Also used for regexes."
      ((and next-is-on-current-line (eq next-type '}))
       (goto-char (swift-mode:token:end next-token))
       (backward-list)
-      (swift-mode:calculate-indent-after-open-curly-brace 0))
+      (swift-mode:calculate-indent-for-curly-bracket 0))
 
      ;; Before ) or ] on the current line
      ((and next-is-on-current-line (memq next-type '(\) \])))
@@ -417,12 +417,56 @@ Also used for regexes."
                      '("switch") nil '("case" "default"))))
         (if (equal (swift-mode:token:text parent) "switch")
             ;; Inside a switch-statement. Aligns with the "switch"
-            (swift-mode:find-parent-and-align-with-next
-             swift-mode:statement-parent-tokens
-             swift-mode:switch-case-offset)
+            (if (swift-mode:bol-other-than-comments-p)
+                (swift-mode:align-with-current-line
+                 swift-mode:switch-case-offset)
+              (swift-mode:find-parent-and-align-with-next
+               swift-mode:statement-parent-tokens
+               swift-mode:switch-case-offset))
           ;; Other cases. Aligns with the previous case.
           (swift-mode:align-with-current-line))))
 
+     ;; Before "else" on the current line
+     ((and next-is-on-current-line (equal next-text "else"))
+      (swift-mode:calculate-indent-before-else))
+
+     ;; After "else"
+     ;;
+     ;; let a =
+     ;;   if x { 1 }
+     ;;   else
+     ;;     if y { 2 }
+     ;;     else { 3 }
+     ;;
+     ;; let a =
+     ;;   if x { 1 } else
+     ;;     if y { 2 } else
+     ;;       { 3 }
+     ;;
+     ;; let a = if x { 1 } else if y { 2 } else
+     ;;   { 3 }
+     ((equal previous-text "else")
+      (goto-char (swift-mode:token:start previous-token))
+      (if (swift-mode:bol-other-than-comments-p)
+          (swift-mode:align-with-current-line
+           swift-mode:multiline-statement-offset)
+        (swift-mode:calculate-indent-before-else
+         swift-mode:multiline-statement-offset)))
+
+     ;; After "if"
+     ;;
+     ;; let a = if
+     ;;           x
+     ;;             .foo() {
+     ;;     1
+     ;; } else {
+     ;;     2
+     ;; }
+     ((equal previous-text "if")
+      (goto-char (swift-mode:token:start previous-token))
+      (swift-mode:align-with-current-line
+       swift-mode:multiline-statement-offset))
+
      ;; After "catch"
      ((equal previous-text "catch")
       (swift-mode:find-parent-and-align-with-next
@@ -432,7 +476,7 @@ Also used for regexes."
      ;; After {
      ((eq previous-type '{)
       (goto-char (swift-mode:token:start previous-token))
-      (swift-mode:calculate-indent-after-open-curly-brace
+      (swift-mode:calculate-indent-for-curly-bracket
        swift-mode:basic-offset))
 
      ;; After (, [,  or < as a open angle bracket
@@ -653,8 +697,8 @@ Also used for regexes."
      ;; After "in" for anonymous function parameters
      ((eq previous-type 'anonymous-function-parameter-in)
       (goto-char (swift-mode:token:start previous-token))
-      (swift-mode:backward-sexps-until-open-curly-brace)
-      (swift-mode:calculate-indent-after-open-curly-brace
+      (swift-mode:backward-sexps-until-open-curly-bracket)
+      (swift-mode:calculate-indent-for-curly-bracket
        swift-mode:basic-offset))
 
      ;; After "in" for "for" statements
@@ -841,14 +885,80 @@ the expression."
                (swift-mode:forward-token-simple))
              (point))))))
 
-(defun swift-mode:calculate-indent-after-open-curly-brace (offset)
-  "Return indentation after open curly braces.
+(defun swift-mode:calculate-indent-before-else (&optional offset)
+  "Return indentation before \"else\" token.
+
+Assuming the cursor is before \"else\".
+OFFSET is extra offset if given."
+  ;; let x = if x { 1 }
+  ;;   else if y { 2 }
+  ;;   else { 3 }
+  ;;
+  ;; let x =
+  ;;   if x { 1 }
+  ;;   else if y { 2 }
+  ;;   else { 3 }
+  ;;
+  ;; let a = if x { 1 }
+  ;;   else
+  ;;     if y { 2 }
+  ;;     else { 3 }
+  ;;
+  ;; let a =
+  ;;   if x { 1 }
+  ;;   else
+  ;;     if y { 2 }
+  ;;     else { 3 }
+  (let ((parent (swift-mode:backward-sexps-until
+                 (append
+                  (remove 'implicit-\; swift-mode:statement-parent-tokens)
+                  '("if")))))
+    (if (equal (swift-mode:token:text parent) "if")
+        (cond
+         ;; Found "if" at the beginning of a line.  Align with it.
+         ;;
+         ;; let a =
+         ;;   if x { 1 }
+         ;;   else
+         ;;     if y { 2 }
+         ;;     else { 3 }
+         ((swift-mode:bol-other-than-comments-p)
+          (swift-mode:align-with-current-line offset))
+
+         ;; Found "else if".
+         ;;
+         ;; let x =
+         ;;   if x { 1 }
+         ;;   else if y { 2 }
+         ;;   else { 3 }
+         ;;
+         ;; let x =
+         ;;   if x { 1 } else if y { 2 }
+         ;;   else { 3 }
+         ((equal (swift-mode:token:text (save-excursion
+                                          (swift-mode:backward-token)))
+                 "else")
+          (swift-mode:backward-token)
+          (if (swift-mode:bol-other-than-comments-p)
+              (swift-mode:align-with-current-line offset)
+            (swift-mode:calculate-indent-before-else offset)))
+
+         ;; let x = if x { 1 }
+         ;;   else { 2 }
+         (t
+          (swift-mode:calculate-indent-of-expression
+           (or offset swift-mode:multiline-statement-offset))))
+      (swift-mode:align-with-current-line offset))))
+
+(defun swift-mode:calculate-indent-for-curly-bracket (offset)
+  "Return indentation relating to curly brackets.
 
-Assuming the cursor is on the open brace.
-OFFSET is the offset of the contents.
-This function is also used for close-curly-brace."
+It is used for indentation after open curly brackets and for close brackets.
+
+Assuming the cursor is on the open bracket.
+OFFSET is the offset of the contents."
   ;; If the statement is multiline expression, aligns with the start of
-  ;; the line on which the open brace is:
+  ;; the line on which the open bracket is:
   ;;
   ;; foo()
   ;;   .then { x in
@@ -860,7 +970,7 @@ This function is also used for close-curly-brace."
   ;;     foo()
   ;;   }
   ;;
-  ;; rather than
+  ;; rather than the start of the statement:
   ;;
   ;; foo()
   ;;   .then { x in
@@ -886,7 +996,7 @@ This function is also used for close-curly-brace."
   ;;       .foo() {
   ;;       }
   ;;
-  ;; Note that curly brace after binary operator is a part of
+  ;; Note that curly bracket after binary operator is a part of
   ;; a multiline expression:
   ;;
   ;; for x in
@@ -930,7 +1040,7 @@ This function is also used for close-curly-brace."
           (cond
            ((member
              (swift-mode:token:text next-token)
-             '("for" "while" "repeat" "switch" "if" "else" "guard"
+             '("for" "while" "repeat" "guard" "switch" "if" "else"
                "defer" "do" "catch"
                "get" "set" "willSet" "didSet" "func" "init" "subscript"
                "enum" "struct" "actor" "class" "extension"
@@ -987,21 +1097,36 @@ This function is also used for close-curly-brace."
             ;; foo {
             ;;   A
             ;;
-            ;; This function is called on the open curly brace.
-            ;; If the close curly brace doesn't exist,
+            ;; This function is called on the open curly bracket.
+            ;; If the close curly bracket doesn't exist,
             ;; swift-mode:forward-token-or-list results in
             ;; "Unbalanced parentheses" error.
-            ;; So if the point is just before the open curly brace,
+            ;; So if the point is just before the open curly bracket,
             ;; exits immediately.
             (forward-comment (point-max))
             (if (< (point) pos)
                 (setq next-token (swift-mode:forward-token-or-list))
               (goto-char (1+ pos))))))))
-    (if is-declaration-or-control-statement-body
+    (cond
+     ((equal (swift-mode:token:text previous-token) "else")
+      (goto-char (swift-mode:token:start previous-token))
+      (swift-mode:calculate-indent-before-else offset))
+
+     ((or (member (swift-mode:token:text next-token) '("if" "switch")))
+      (goto-char (swift-mode:token:start next-token))
+      (if (swift-mode:bol-other-than-comments-p)
+          (swift-mode:align-with-current-line offset)
         (swift-mode:find-parent-and-align-with-next
          swift-mode:statement-parent-tokens
-         offset)
-      (swift-mode:calculate-indent-of-expression offset offset))))
+         offset)))
+
+     (is-declaration-or-control-statement-body
+      (swift-mode:find-parent-and-align-with-next
+       swift-mode:statement-parent-tokens
+       offset))
+
+     (t
+      (swift-mode:calculate-indent-of-expression offset offset)))))
 
 (defun swift-mode:calculate-indent-of-prefix-comma ()
   "Return indentation for prefix comma.
@@ -1353,7 +1478,7 @@ is the symbol `any', it matches all tokens."
       (setq text (swift-mode:token:text parent)))
     parent))
 
-(defun swift-mode:backward-sexps-until-open-curly-brace ()
+(defun swift-mode:backward-sexps-until-open-curly-bracket ()
   "Backward sexps until an open curly brace appears.
 Return the brace token.
 When this function returns, the cursor is at the start of the token.
diff --git a/swift-mode-lexer.el b/swift-mode-lexer.el
index 60719c0ccf..fd3ebfce73 100644
--- a/swift-mode-lexer.el
+++ b/swift-mode-lexer.el
@@ -640,6 +640,12 @@ return non-nil."
                                       (swift-mode:forward-token-simple)))
              "let"))
 
+     ;; Suppress implicit semicolon around else
+     ((or
+       (equal (swift-mode:token:text previous-token) "else")
+       (equal (swift-mode:token:text next-token) "else"))
+      nil)
+
      ;; Inserts semicolon before open curly bracket.
      ;;
      ;; Open curly bracket may continue the previous line, but we do not indent
@@ -718,7 +724,7 @@ return non-nil."
      ;; Inserts implicit semicolon before keywords that starts a new
      ;; statement.
      ((member (swift-mode:token:text next-token)
-              '("for" "repeat" "switch"  "case" "default" "defer" "do" "if"
+              '("for" "repeat" "case" "default" "defer" "do"
                 "guard" "let" "var" "throw" "import" "return"))
       t)
 
@@ -740,12 +746,6 @@ return non-nil."
                      (swift-mode:backward-token-simple)))
                    "repeat"))))))
 
-     ;; Inserts implicit semicolon around else
-     ((or
-       (equal (swift-mode:token:text previous-token) "else")
-       (equal (swift-mode:token:text next-token) "else"))
-      t)
-
      ;; Inserts implicit semicolon before keywords that behave like method
      ;; names.
      ((member (swift-mode:token:text next-token)
diff --git a/test/swift-files/indent/expressions.swift 
b/test/swift-files/indent/expressions.swift
index 50d9be8b96..21e2c6abda 100644
--- a/test/swift-files/indent/expressions.swift
+++ b/test/swift-files/indent/expressions.swift
@@ -619,3 +619,205 @@ let x = foo.bar {
 } baz: {
     aaa()
 }
+
+let x =
+  foo
+  .bar {
+      aaa()
+  } baz: {
+      aaa()
+  }
+
+
+// If expression and Switch expression
+// 
https://github.com/apple/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md
+
+let a =
+  if x { 1 }
+  else if y { 2 }
+  else { 3 }
+
+let a = if x { 1 }
+  else if y { 2 }
+  else { 3 }
+
+let a = if x
+    { 1 } // swift-mode:test:known-bug
+  else if y
+    { 2 }
+  else
+    { 3 }
+
+let a = if x { 1 } else if y { 2 } // swift-mode:test:known-bug
+  else { 3 }
+
+let a = if x { 1 } else if y { 2 } else
+  { 3 }
+
+let a = if x { 1 } else
+  if y { 2 } else
+    { 3 }
+
+let a = if x { 1 }
+  else
+    if y { 2 }
+    else { 3 }
+
+let a =
+  if x { 1 }
+  else
+    if y { 2 }
+    else { 3 }
+
+let a =
+  if x { 1 } else
+    if y { 2 } else
+      { 3 }
+
+let a = if x {
+    1
+} else if y {
+    2
+} else {
+    3
+}
+
+let a = if
+  x {
+    1
+} else if y {
+    2
+} else {
+    3
+}
+
+let a = if x
+             .foo()
+             .bar() { foo() }
+  else { bar() }
+
+let a = if x
+             .foo()
+             .bar() {
+    foo()
+} else {
+    bar()
+}
+
+let a =
+  if
+    x
+      .foo()
+      .bar() { foo() }
+  else { bar() }
+
+let a =
+  if
+    x
+      .foo()
+      .bar() {
+      foo()
+  } else {
+      bar()
+  }
+
+let a = if
+  let
+    x
+    =
+    xx,
+  var
+    y
+    =
+    yy,
+  x
+    ==
+    y,
+  case
+    (
+      a,
+      b
+    )
+    =
+    ab {
+    foo()
+} else {
+    bar()
+}
+
+func foo() -> Int {
+    return if x { bar() }
+      else { baz() }
+}
+
+func foo() -> Int {
+    return
+      if x { bar() }
+      else { baz() }
+}
+
+func foo() -> Int {
+    return
+      if x {
+          bar()
+      }
+      else {
+          baz()
+      }
+}
+
+func foo() -> Int {
+    return if x {
+        bar()
+    }
+      else {
+        baz()
+    }
+}
+
+let x = switch foo.bar {
+case foo:
+    foo()
+      .bar()
+default:
+    foo()
+}
+
+
+let x = switch foo
+  .bar {
+case foo:
+    foo()
+default:
+    foo()
+}
+
+let x = switch
+  foo
+  .bar {
+case foo:
+    foo()
+    foo()
+default:
+    foo()
+    foo()
+}
+
+func foo() {
+    return switch x {
+    case 1:
+        1
+    default:
+        2
+    }
+}
+
+func foo() {
+    return
+      switch x {
+      case 1:
+          1
+      default:
+          2
+      }
+}
diff --git a/test/swift-files/indent/identifiers.swift 
b/test/swift-files/indent/identifiers.swift
index 481bf23682..b960169899 100644
--- a/test/swift-files/indent/identifiers.swift
+++ b/test/swift-files/indent/identifiers.swift
@@ -139,7 +139,7 @@ func foo() {
       do: 1
     )
     foo(
-      else: 1
+      else: 1 // swift-mode:test:known-bug
     )
     foo(
       fallthrough: 1
diff --git a/test/swift-files/indent/statements.swift 
b/test/swift-files/indent/statements.swift
index 2778c27475..59d9105851 100644
--- a/test/swift-files/indent/statements.swift
+++ b/test/swift-files/indent/statements.swift
@@ -1077,17 +1077,27 @@ switch
 // Labeled statements
 
 
+// "if" and "switch" are now expression in limited contexts.
+// Should we indent them like statements like "for" or expressions like this?
+
 foo:
   if foo
        .bar == baz {
-}
+  }
 
 foo:
   if
-  foo
-    .bar == baz {
-}
+    foo
+      .bar == baz {
+  }
 
+foo:
+  switch x {
+  case 1:
+      1
+  default:
+      2
+  }
 
 foo:
   for

Reply via email to