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