branch: elpa/swift-mode
commit 25e41ed7d405ea566a45fef7cdb673b86ae01701
Author: taku0 <[email protected]>
Commit: taku0 <[email protected]>
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