* [PATCH] userdiff: add support for Swift
@ 2026-07-17 14:02 Shlok Kulshreshtha
2026-07-17 16:27 ` Junio C Hamano
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Shlok Kulshreshtha @ 2026-07-17 14:02 UTC (permalink / raw)
To: git
Cc: Shlok Kulshreshtha, D. Ben Knoble, Junio C Hamano,
René Scharfe, Johannes Sixt, Eric Sunshine, Scott L. Burson
Add a built-in userdiff driver for the Swift programming language so that
diff hunk headers and word diffs work out of the box for ".swift" files.
The funcname pattern is built for Swift's own declaration grammar: an
optional run of attributes ("@objc", "@available(iOS 13, *)", ...),
followed by an optional run of lowercase modifiers ("public", "static",
"final", ...), followed by a declaration keyword (func, class, struct,
enum, protocol, extension, actor, init, deinit, subscript). The keyword
is followed by a boundary that allows whitespace, "(" (init/subscript),
"?" or "!" (failable init), or "<" (generics), while still acting as a
word boundary so e.g. "initialize(" does not match.
The word regex recognizes Swift identifiers, hexadecimal, octal, binary,
integer and floating-point literals, and the language's operators.
Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
---
This addresses the "add a userdiff driver for a language" microproject.
Swift is not covered by a built-in driver yet, and I did not find an
in-flight patch adding one on the list; please let me know if one exists.
Motivation: without a Swift driver, ".swift" files use the generic
funcname heuristic, so "git diff" hunk headers name the enclosing type
instead of the changed function.
The pattern is built directly from Swift's declaration grammar rather
than adapted from another language's driver, so it covers a few things
that a straight port would miss:
- attributes, with or without arguments, whether on their own line
("@objc" above a "func") or inline with the declaration
("@objc func foo()", "@available(iOS 13, *) public func bar()");
- modifiers ("public", "static", "override", "mutating", ...) in any
combination, before or after attributes;
- failable initializers, "init?" and "init!";
- generics, "init<T>" and "subscript<T>";
- the keyword boundary still acts as a word boundary, so e.g.
"initialize(" is not mistaken for "init".
I verified all of the above against a built binary, including generic
functions with "where" clauses, multi-line signatures, and operator
functions such as "static func ==". I did not find a real case this
pattern misses; the ones I intentionally left out are one-line
declarations ("typealias", "associatedtype") and rarer forms
("operator", "precedencegroup", "macro"), which would not make useful
section headers anyway.
The word regex covers Swift identifiers; hexadecimal, octal, binary,
integer and floating-point literals; and operators including "<<=" /
">>=", "??" and the range operators. All of t4018 passes, including the
sorted builtin_drivers check and the new swift-* fixtures.
Documentation/gitattributes.adoc | 2 ++
t/t4018/swift-actor | 5 +++++
t/t4018/swift-attribute-with-args | 7 +++++++
t/t4018/swift-class | 5 +++++
t/t4018/swift-enum | 5 +++++
t/t4018/swift-extension | 5 +++++
t/t4018/swift-failable-init | 7 +++++++
t/t4018/swift-func | 5 +++++
t/t4018/swift-generic-subscript | 7 +++++++
t/t4018/swift-init | 7 +++++++
t/t4018/swift-inline-attribute | 7 +++++++
t/t4018/swift-modifiers | 4 ++++
t/t4018/swift-protocol | 5 +++++
t/t4018/swift-struct | 5 +++++
userdiff.c | 10 ++++++++++
15 files changed, 86 insertions(+)
create mode 100644 t/t4018/swift-actor
create mode 100644 t/t4018/swift-attribute-with-args
create mode 100644 t/t4018/swift-class
create mode 100644 t/t4018/swift-enum
create mode 100644 t/t4018/swift-extension
create mode 100644 t/t4018/swift-failable-init
create mode 100644 t/t4018/swift-func
create mode 100644 t/t4018/swift-generic-subscript
create mode 100644 t/t4018/swift-init
create mode 100644 t/t4018/swift-inline-attribute
create mode 100644 t/t4018/swift-modifiers
create mode 100644 t/t4018/swift-protocol
create mode 100644 t/t4018/swift-struct
diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc
index bd76167a45..9fea75f96f 100644
--- a/Documentation/gitattributes.adoc
+++ b/Documentation/gitattributes.adoc
@@ -914,6 +914,8 @@ patterns are available:
- `scheme` suitable for source code in most Lisp dialects,
including Scheme, Emacs Lisp, Common Lisp, and Clojure.
+- `swift` suitable for source code in the Swift language.
+
- `tex` suitable for source code for LaTeX documents.
diff --git a/t/t4018/swift-actor b/t/t4018/swift-actor
new file mode 100644
index 0000000000..e4852f40a7
--- /dev/null
+++ b/t/t4018/swift-actor
@@ -0,0 +1,5 @@
+actor RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-attribute-with-args b/t/t4018/swift-attribute-with-args
new file mode 100644
index 0000000000..22b1ee32f1
--- /dev/null
+++ b/t/t4018/swift-attribute-with-args
@@ -0,0 +1,7 @@
+struct View {
+ @available(iOS 13, *) public func RIGHT() {
+ let a = 1
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-class b/t/t4018/swift-class
new file mode 100644
index 0000000000..c3a9336027
--- /dev/null
+++ b/t/t4018/swift-class
@@ -0,0 +1,5 @@
+class RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-enum b/t/t4018/swift-enum
new file mode 100644
index 0000000000..0a84302993
--- /dev/null
+++ b/t/t4018/swift-enum
@@ -0,0 +1,5 @@
+enum RIGHT {
+ case first
+ // a comment
+ case ChangeMe
+}
diff --git a/t/t4018/swift-extension b/t/t4018/swift-extension
new file mode 100644
index 0000000000..cbc18ab6ef
--- /dev/null
+++ b/t/t4018/swift-extension
@@ -0,0 +1,5 @@
+extension RIGHT {
+ static let a = 1
+ // a comment
+ static let b = ChangeMe
+}
diff --git a/t/t4018/swift-failable-init b/t/t4018/swift-failable-init
new file mode 100644
index 0000000000..5e4091d97c
--- /dev/null
+++ b/t/t4018/swift-failable-init
@@ -0,0 +1,7 @@
+class Bar {
+ init?(RIGHT: Int) {
+ let value = RIGHT
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-func b/t/t4018/swift-func
new file mode 100644
index 0000000000..1fecae0911
--- /dev/null
+++ b/t/t4018/swift-func
@@ -0,0 +1,5 @@
+func RIGHT(x: Int) -> Int {
+ let y = x
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-generic-subscript b/t/t4018/swift-generic-subscript
new file mode 100644
index 0000000000..565f93cd6c
--- /dev/null
+++ b/t/t4018/swift-generic-subscript
@@ -0,0 +1,7 @@
+struct Container {
+ subscript<RIGHT>(index: RIGHT) -> Int {
+ let a = 0
+ // a comment
+ return ChangeMe
+ }
+}
diff --git a/t/t4018/swift-init b/t/t4018/swift-init
new file mode 100644
index 0000000000..f683e74794
--- /dev/null
+++ b/t/t4018/swift-init
@@ -0,0 +1,7 @@
+class Foo {
+ init(RIGHT: Int) {
+ let value = RIGHT
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-inline-attribute b/t/t4018/swift-inline-attribute
new file mode 100644
index 0000000000..2374c4b603
--- /dev/null
+++ b/t/t4018/swift-inline-attribute
@@ -0,0 +1,7 @@
+class Service {
+ @objc func RIGHT() {
+ let path = "/api"
+ // a comment
+ log(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-modifiers b/t/t4018/swift-modifiers
new file mode 100644
index 0000000000..9d80685a78
--- /dev/null
+++ b/t/t4018/swift-modifiers
@@ -0,0 +1,4 @@
+public static func RIGHT() -> Int {
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-protocol b/t/t4018/swift-protocol
new file mode 100644
index 0000000000..07c39ec2a3
--- /dev/null
+++ b/t/t4018/swift-protocol
@@ -0,0 +1,5 @@
+protocol RIGHT {
+ var first: Int { get }
+ // a comment
+ var second: ChangeMe { get }
+}
diff --git a/t/t4018/swift-struct b/t/t4018/swift-struct
new file mode 100644
index 0000000000..e399ed7759
--- /dev/null
+++ b/t/t4018/swift-struct
@@ -0,0 +1,5 @@
+struct RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/userdiff.c b/userdiff.c
index b5412e6bc3..df37dd78a6 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -362,6 +362,16 @@ PATTERNS("scheme",
"\\|([^|\\\\]|\\\\.)*\\|"
/* All other words should be delimited by spaces or parentheses. */
"|([^][)(}{ \t])+"),
+PATTERNS("swift",
+ "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ /* hexadecimal, octal, and binary literals */
+ "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+"
+ /* integers and floating-point numbers */
+ "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
+ /* unary and binary operators */
+ "|[-+*/%<>=!&|^~?]=?|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
"\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"),
{ .name = "default", .binary = -1 },
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH] userdiff: add support for Swift
2026-07-17 14:02 [PATCH] userdiff: add support for Swift Shlok Kulshreshtha
@ 2026-07-17 16:27 ` Junio C Hamano
2026-07-18 18:11 ` Johannes Sixt
2026-07-21 6:57 ` [PATCH v2] " Shlok Kulshreshtha
2 siblings, 0 replies; 7+ messages in thread
From: Junio C Hamano @ 2026-07-17 16:27 UTC (permalink / raw)
To: Shlok Kulshreshtha
Cc: git, D. Ben Knoble, René Scharfe, Johannes Sixt,
Eric Sunshine, Scott L. Burson
Shlok Kulshreshtha <diy2903@gmail.com> writes:
> Add a built-in userdiff driver for the Swift programming language so that
> diff hunk headers and word diffs work out of the box for ".swift" files.
I do not work with this language myself, so I have no idea how well
the pattern proposed here matches real-world code. Will queue, but
it needs reviews and validaion from active Swift users before it can
advance to 'next' and eventually to a future Git release.
Thanks.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] userdiff: add support for Swift
2026-07-17 14:02 [PATCH] userdiff: add support for Swift Shlok Kulshreshtha
2026-07-17 16:27 ` Junio C Hamano
@ 2026-07-18 18:11 ` Johannes Sixt
2026-07-20 4:55 ` Junio C Hamano
` (2 more replies)
2026-07-21 6:57 ` [PATCH v2] " Shlok Kulshreshtha
2 siblings, 3 replies; 7+ messages in thread
From: Johannes Sixt @ 2026-07-18 18:11 UTC (permalink / raw)
To: Shlok Kulshreshtha
Cc: D. Ben Knoble, Junio C Hamano, René Scharfe, Eric Sunshine,
Scott L. Burson, git
Am 17.07.26 um 16:02 schrieb Shlok Kulshreshtha:
> Add a built-in userdiff driver for the Swift programming language so that
> diff hunk headers and word diffs work out of the box for ".swift" files.
>
> The funcname pattern is built for Swift's own declaration grammar: an
> optional run of attributes ("@objc", "@available(iOS 13, *)", ...),
> followed by an optional run of lowercase modifiers ("public", "static",
> "final", ...), followed by a declaration keyword (func, class, struct,
> enum, protocol, extension, actor, init, deinit, subscript). The keyword
> is followed by a boundary that allows whitespace, "(" (init/subscript),
> "?" or "!" (failable init), or "<" (generics), while still acting as a
> word boundary so e.g. "initialize(" does not match.
>
> The word regex recognizes Swift identifiers, hexadecimal, octal, binary,
> integer and floating-point literals, and the language's operators.
>
> Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
> ---
> This addresses the "add a userdiff driver for a language" microproject.
I am mildly surprised that userdiff drivers can count as microproject.
At a minimum, they are on the challenging side of the spectrum.
> Swift is not covered by a built-in driver yet, and I did not find an
> in-flight patch adding one on the list; please let me know if one exists.
>
> Motivation: without a Swift driver, ".swift" files use the generic
> funcname heuristic, so "git diff" hunk headers name the enclosing type
> instead of the changed function.
>
> The pattern is built directly from Swift's declaration grammar rather
> than adapted from another language's driver, so it covers a few things
> that a straight port would miss:
>
> - attributes, with or without arguments, whether on their own line
> ("@objc" above a "func") or inline with the declaration
> ("@objc func foo()", "@available(iOS 13, *) public func bar()");
AFAIC, the regular expression does not match attributes on their own
line. What relevance does this statement have?
> - modifiers ("public", "static", "override", "mutating", ...) in any
> combination, before or after attributes;
> - failable initializers, "init?" and "init!";
> - generics, "init<T>" and "subscript<T>";
> - the keyword boundary still acts as a word boundary, so e.g.
> "initialize(" is not mistaken for "init".
>
> I verified all of the above against a built binary, including generic
> functions with "where" clauses, multi-line signatures, and operator
> functions such as "static func ==". I did not find a real case this
> pattern misses; the ones I intentionally left out are one-line
> declarations ("typealias", "associatedtype") and rarer forms
> ("operator", "precedencegroup", "macro"), which would not make useful
> section headers anyway.
>
> The word regex covers Swift identifiers; hexadecimal, octal, binary,
> integer and floating-point literals; and operators including "<<=" /
> ">>=", "??" and the range operators. All of t4018 passes, including the
> sorted builtin_drivers check and the new swift-* fixtures.
>
> Documentation/gitattributes.adoc | 2 ++
> t/t4018/swift-actor | 5 +++++
> t/t4018/swift-attribute-with-args | 7 +++++++
> t/t4018/swift-class | 5 +++++
> t/t4018/swift-enum | 5 +++++
> t/t4018/swift-extension | 5 +++++
> t/t4018/swift-failable-init | 7 +++++++
> t/t4018/swift-func | 5 +++++
> t/t4018/swift-generic-subscript | 7 +++++++
> t/t4018/swift-init | 7 +++++++
> t/t4018/swift-inline-attribute | 7 +++++++
> t/t4018/swift-modifiers | 4 ++++
> t/t4018/swift-protocol | 5 +++++
> t/t4018/swift-struct | 5 +++++
> userdiff.c | 10 ++++++++++
> 15 files changed, 86 insertions(+)
> create mode 100644 t/t4018/swift-actor
> create mode 100644 t/t4018/swift-attribute-with-args
> create mode 100644 t/t4018/swift-class
> create mode 100644 t/t4018/swift-enum
> create mode 100644 t/t4018/swift-extension
> create mode 100644 t/t4018/swift-failable-init
> create mode 100644 t/t4018/swift-func
> create mode 100644 t/t4018/swift-generic-subscript
> create mode 100644 t/t4018/swift-init
> create mode 100644 t/t4018/swift-inline-attribute
> create mode 100644 t/t4018/swift-modifiers
> create mode 100644 t/t4018/swift-protocol
> create mode 100644 t/t4018/swift-struct
>
> diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc
> index bd76167a45..9fea75f96f 100644
> --- a/Documentation/gitattributes.adoc
> +++ b/Documentation/gitattributes.adoc
> @@ -914,6 +914,8 @@ patterns are available:
> - `scheme` suitable for source code in most Lisp dialects,
> including Scheme, Emacs Lisp, Common Lisp, and Clojure.
>
> +- `swift` suitable for source code in the Swift language.
> +
> - `tex` suitable for source code for LaTeX documents.
>
>
> diff --git a/t/t4018/swift-actor b/t/t4018/swift-actor
> new file mode 100644
> index 0000000000..e4852f40a7
> --- /dev/null
> +++ b/t/t4018/swift-actor
> @@ -0,0 +1,5 @@
> +actor RIGHT {
> + let a = 1
> + // a comment
> + let b = ChangeMe
> +}
> diff --git a/t/t4018/swift-attribute-with-args b/t/t4018/swift-attribute-with-args
> new file mode 100644
> index 0000000000..22b1ee32f1
> --- /dev/null
> +++ b/t/t4018/swift-attribute-with-args
> @@ -0,0 +1,7 @@
> +struct View {
> + @available(iOS 13, *) public func RIGHT() {
> + let a = 1
> + // a comment
> + print(ChangeMe)
> + }
> +}
So, this doesn't pick up the "struct View {" line, but the line below.
Good test.
> diff --git a/t/t4018/swift-failable-init b/t/t4018/swift-failable-init
> new file mode 100644
> index 0000000000..5e4091d97c
> --- /dev/null
> +++ b/t/t4018/swift-failable-init
> @@ -0,0 +1,7 @@
> +class Bar {
> + init?(RIGHT: Int) {
> + let value = RIGHT
> + // a comment
> + print(ChangeMe)
> + }
> +}
This test contains "RIGHT" twice. This is not good, because we do not
know which one is picked.
> diff --git a/t/t4018/swift-generic-subscript b/t/t4018/swift-generic-subscript
> new file mode 100644
> index 0000000000..565f93cd6c
> --- /dev/null
> +++ b/t/t4018/swift-generic-subscript
> @@ -0,0 +1,7 @@
> +struct Container {
> + subscript<RIGHT>(index: RIGHT) -> Int {
> + let a = 0
> + // a comment
> + return ChangeMe
> + }
> +}
It is strange to have "RIGHT" twice on the same line, but it does no
harm. Still, there should be only one for consistency.
> diff --git a/t/t4018/swift-init b/t/t4018/swift-init
> new file mode 100644
> index 0000000000..f683e74794
> --- /dev/null
> +++ b/t/t4018/swift-init
> @@ -0,0 +1,7 @@
> +class Foo {
> + init(RIGHT: Int) {
> + let value = RIGHT
> + // a comment
> + print(ChangeMe)
> + }
> +}
Again "RIGHT" twice in a harmful way.
All other test cases look good.
> diff --git a/userdiff.c b/userdiff.c
> index b5412e6bc3..df37dd78a6 100644
> --- a/userdiff.c
> +++ b/userdiff.c
> @@ -362,6 +362,16 @@ PATTERNS("scheme",
> "\\|([^|\\\\]|\\\\.)*\\|"
> /* All other words should be delimited by spaces or parentheses. */
> "|([^][)(}{ \t])+"),
> +PATTERNS("swift",
> + "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
This looks good.
Notice, however, how the regular expression matcher has to backtrack on
even simple lines such as
class foo {
On the first attempt, [a-z]+ matches "class", but then "foo" does not
match. On the next attempt, the clause with [a-z]+ matches zero times
and the next clause matches "class" and, in total, successfully.
It may be worth considering to enumerate all keywords and permit any run
of them:
(public|final|etc.|func|init|...|actor)[ \t(?!<]+)+
It does not matter that this would match any assemblement of keywords;
they wouldn't occur in correct Swift code anyway. (Or would they?)
> + /* -- */
> + "[a-zA-Z_][a-zA-Z0-9_]*"
> + /* hexadecimal, octal, and binary literals */
> + "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+"
You could just throw all of them into a single pattern like this:
0[xXoObB][0-9a-fA-F_]+
except when, for example,
0b1_abc
can occur in correct Swift code (perhaps the token 0b1 followd by the
token _abc).
> + /* integers and floating-point numbers */
> + "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
Ok. Is ".5" a correct floating-point number? If so, it would not be
caught by this regular expression? But it wouldn't be particularly
harmful, either, to leave it as is. The ".5" would just be parsed as two
tokens, "." and "5".
> + /* unary and binary operators */
> + "|[-+*/%<>=!&|^~?]=?|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
You do not have to account for single-character operators; they are
automatic. Drop the "?" from the first "=?".
These are my comments on the technical side. Since I do not speak Swift,
I cannot comment on how reasonable your choice which lines to pick out is.
-- Hannes
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] userdiff: add support for Swift
2026-07-18 18:11 ` Johannes Sixt
@ 2026-07-20 4:55 ` Junio C Hamano
2026-07-20 8:49 ` Johannes Sixt
2026-07-20 9:52 ` Shlok Kulshreshtha
2 siblings, 0 replies; 7+ messages in thread
From: Junio C Hamano @ 2026-07-20 4:55 UTC (permalink / raw)
To: Johannes Sixt
Cc: Shlok Kulshreshtha, D. Ben Knoble, René Scharfe,
Eric Sunshine, Scott L. Burson, git
Johannes Sixt <j6t@kdbg.org> writes:
> Am 17.07.26 um 16:02 schrieb Shlok Kulshreshtha:
>> Add a built-in userdiff driver for the Swift programming language so that
>> diff hunk headers and word diffs work out of the box for ".swift" files.
>>
>> The funcname pattern is built for Swift's own declaration grammar: an
>> optional run of attributes ("@objc", "@available(iOS 13, *)", ...),
>> followed by an optional run of lowercase modifiers ("public", "static",
>> "final", ...), followed by a declaration keyword (func, class, struct,
>> enum, protocol, extension, actor, init, deinit, subscript). The keyword
>> is followed by a boundary that allows whitespace, "(" (init/subscript),
>> "?" or "!" (failable init), or "<" (generics), while still acting as a
>> word boundary so e.g. "initialize(" does not match.
>>
>> The word regex recognizes Swift identifiers, hexadecimal, octal, binary,
>> integer and floating-point literals, and the language's operators.
>>
>> Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
>> ---
>> This addresses the "add a userdiff driver for a language" microproject.
>
> I am mildly surprised that userdiff drivers can count as microproject.
> At a minimum, they are on the challenging side of the spectrum.
I am, too ;-) It is hard to get them right, even though thanks to
your earlier work long time ago, writing a test that demonstrates
what is expected of the patterns is fairly easy to write.
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] userdiff: add support for Swift
2026-07-18 18:11 ` Johannes Sixt
2026-07-20 4:55 ` Junio C Hamano
@ 2026-07-20 8:49 ` Johannes Sixt
2026-07-20 9:52 ` Shlok Kulshreshtha
2 siblings, 0 replies; 7+ messages in thread
From: Johannes Sixt @ 2026-07-20 8:49 UTC (permalink / raw)
To: Shlok Kulshreshtha
Cc: D. Ben Knoble, Junio C Hamano, René Scharfe, Eric Sunshine,
Scott L. Burson, git
Am 18.07.26 um 20:11 schrieb Johannes Sixt:
> Am 17.07.26 um 16:02 schrieb Shlok Kulshreshtha:
>> +PATTERNS("swift",
>> + "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
>
> This looks good.
>
> Notice, however, how the regular expression matcher has to backtrack on
> even simple lines such as
[...]
> It may be worth considering to enumerate all keywords and permit any run
> of them:
Let me back-paddle on this one. As I said, the original RE is good. I am
making up a problem here without providing evidence. Modern RE matchers
may be clever enough that there is no problem. If it turns out there is
a problem, we can improve later something that already works.
-- Hannes
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] userdiff: add support for Swift
2026-07-18 18:11 ` Johannes Sixt
2026-07-20 4:55 ` Junio C Hamano
2026-07-20 8:49 ` Johannes Sixt
@ 2026-07-20 9:52 ` Shlok Kulshreshtha
2 siblings, 0 replies; 7+ messages in thread
From: Shlok Kulshreshtha @ 2026-07-20 9:52 UTC (permalink / raw)
To: j6t
Cc: Shlok Kulshreshtha, D. Ben Knoble, Junio C Hamano,
René Scharfe, Eric Sunshine, Scott L. Burson, git
Johannes Sixt <j6t@kdbg.org> writes:
>> - attributes, with or without arguments, whether on their own line
>> ("@objc" above a "func") or inline with the declaration
> AFAIC, the regular expression does not match attributes on their own
> line. What relevance does this statement have?
You are right. The pattern only matches attributes that are inline with
the declaration. An attribute on its own line is not matched, and does
not need to be, because the declaration line below it matches on its own.
I have reworded this.
> This test contains "RIGHT" twice. This is not good, because we do not
> know which one is picked.
[...]
> Again "RIGHT" twice in a harmful way.
Fixed in a coming v2: swift-init, swift-failable-init and
swift-generic-subscript now contain "RIGHT" only once, on the
declaration line.
> It may be worth considering to enumerate all keywords and permit any
> run of them:
> (public|final|etc.|func|init|...|actor)[ \t(?!<]+)+
Noted, and thanks for the follow-up on this one. I did check it anyway
out of curiosity: with that shape, a line that is only modifiers and
never reaches a real declaration keyword, such as
public var counter = 0
would still match, because it merges modifiers and declaration keywords
into one interchangeable run. The current pattern requires a real
keyword at the end, so that line correctly gets no header. I will keep
the current form for now, and can revisit if the backtracking turns out
to matter in practice.
> You could just throw all of them into a single pattern like this:
> 0[xXoObB][0-9a-fA-F_]+
> except when, for example, 0b1_abc
Right -- that is why I kept them as three separate patterns, so the
digit ranges stay correct (binary [01], octal [0-7]); merging would
mis-tokenize "0b1_abc".
> Is ".5" a correct floating-point number?
No -- Swift requires a leading digit, so ".5" is a syntax error (one must
write "0.5"). Tokenizing it as "." and "5" is therefore fine, and it
does not occur in valid Swift.
> You do not have to account for single-character operators; they are
> automatic. Drop the "?" from the first "=?".
Done in a coming v2, thanks; I had not realized PATTERNS appends
"|[^[:space:]]". It is a nice simplification, and it only touches the
word regex, not the funcname pattern.
Since neither of us speaks Swift, for your ease of judgement I have also
put together some coverage numbers, which the coming v2 cover note will
include:
- Grammar: I went through every declaration form listed in the "Summary
of the Grammar" in Swift's own language reference (func, init incl.
failable/generic, deinit, subscript incl. generic, class, struct,
enum, protocol, extension, actor, operator methods, stacked
modifiers, attributes with and without arguments, "where" clauses,
multi-line signatures -- 26 forms total) and wrote a case for each.
All 26 get the correct header.
- Real-world code: I ran the driver over the last 200 commits touching
*.swift in seven different Swift projects -- Alamofire,
apple/swift-argument-parser, vapor, Kingfisher, RxSwift, SnapKit, and
pointfreeco/swift-composable-architecture -- and checked every hunk
header by hand. Out of 20454 hunks, 15310 got a header, and 15296 of
those (99.9%) named a real declaration. None of the empty-header
hunks turned out to be a real miss (they were things like file
comment blocks, imports, or Package.swift, which have nothing to
attach a header to).
These numbers are unaffected by the changes in this reply: the funcname
pattern is identical in v1 and v2 (only the word regex and the test
files changed), and both measurements are of hunk headers, which come
from the funcname pattern alone. So the coverage above still holds for
v2.
Besides the fixes above, v2 will also carry the reworded attribute
description and the changelog explaining what changed since v1, so the
full picture is in one place when you look at it.
Thanks for the careful review.
Shlok
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2] userdiff: add support for Swift
2026-07-17 14:02 [PATCH] userdiff: add support for Swift Shlok Kulshreshtha
2026-07-17 16:27 ` Junio C Hamano
2026-07-18 18:11 ` Johannes Sixt
@ 2026-07-21 6:57 ` Shlok Kulshreshtha
2 siblings, 0 replies; 7+ messages in thread
From: Shlok Kulshreshtha @ 2026-07-21 6:57 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Johannes Sixt, D . Ben Knoble, René Scharfe,
Eric Sunshine, Scott L . Burson, Shlok Kulshreshtha
Add a built-in userdiff driver for the Swift programming language so that
diff hunk headers and word diffs work out of the box for ".swift" files.
The funcname pattern is built for Swift's own declaration grammar: an
optional run of attributes ("@objc", "@available(iOS 13, *)", ...),
followed by an optional run of lowercase modifiers ("public", "static",
"final", ...), followed by a declaration keyword (func, class, struct,
enum, protocol, extension, actor, init, deinit, subscript). The keyword
is followed by a boundary that allows whitespace, "(" (init/subscript),
"?" or "!" (failable init), or "<" (generics), while still acting as a
word boundary so e.g. "initialize(" does not match.
The word regex recognizes Swift identifiers, hexadecimal, octal, binary,
integer and floating-point literals, and the language's operators.
Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
---
v2, addressing Johannes Sixt's review of v1
(<2a3a73c5-5e90-44a3-bf6a-6e98ce5e5a59@kdbg.org>). Changes since v1:
- t4018/swift-{init,failable-init,generic-subscript}: "RIGHT" now
appears only once, on the declaration line, so the expected header is
unambiguous.
- word regex: dropped the redundant "?" after the single-character
operator class. Single characters are already covered by the
"|[^[:space:]]" fallback that the PATTERNS macro appends, so only the
two-character forms need to be spelled out.
(A couple of Hannes's other suggestions I kept as-is; I have explained
the reasoning in a reply to his review.)
Some coverage evidence beyond the t4018 fixtures:
- Grammar: a test over every declaration form in Swift's grammar
summary (26 forms -- func/class/struct/enum/protocol/extension/actor,
init incl. "init?"/"init!"/generic, deinit, subscript incl. generic,
operator methods, stacked modifiers, inline attributes with and
without arguments, "where" clauses, multi-line signatures) -- all 26
resolve to the correct declaration.
- Corpus: run over the last 200 commits touching *.swift in seven
stylistically different projects (Alamofire, apple/
swift-argument-parser, vapor, Kingfisher, RxSwift, SnapKit,
pointfreeco/swift-composable-architecture): of 20454 hunks, 15310
produced a header and 15296 (99.9%) named a real declaration. The
empty-header hunks are changes with no enclosing declaration (file
comment blocks, imports, Package.swift, top-level code); sampling
found no change inside a declaration that failed to get a header.
The handful of non-declaration headers are the selective-import form
("import class Foundation.Bundle"), which reads "import" as a
modifier; rare and low-harm, and I can exclude it in a follow-up if
preferred.
Documentation/gitattributes.adoc | 2 ++
t/t4018/swift-actor | 5 +++++
t/t4018/swift-attribute-with-args | 7 +++++++
t/t4018/swift-class | 5 +++++
t/t4018/swift-enum | 5 +++++
t/t4018/swift-extension | 5 +++++
t/t4018/swift-failable-init | 7 +++++++
t/t4018/swift-func | 5 +++++
t/t4018/swift-generic-subscript | 7 +++++++
t/t4018/swift-init | 7 +++++++
t/t4018/swift-inline-attribute | 7 +++++++
t/t4018/swift-modifiers | 4 ++++
t/t4018/swift-protocol | 5 +++++
t/t4018/swift-struct | 5 +++++
userdiff.c | 10 ++++++++++
15 files changed, 86 insertions(+)
create mode 100644 t/t4018/swift-actor
create mode 100644 t/t4018/swift-attribute-with-args
create mode 100644 t/t4018/swift-class
create mode 100644 t/t4018/swift-enum
create mode 100644 t/t4018/swift-extension
create mode 100644 t/t4018/swift-failable-init
create mode 100644 t/t4018/swift-func
create mode 100644 t/t4018/swift-generic-subscript
create mode 100644 t/t4018/swift-init
create mode 100644 t/t4018/swift-inline-attribute
create mode 100644 t/t4018/swift-modifiers
create mode 100644 t/t4018/swift-protocol
create mode 100644 t/t4018/swift-struct
diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc
index bd76167a45..9fea75f96f 100644
--- a/Documentation/gitattributes.adoc
+++ b/Documentation/gitattributes.adoc
@@ -914,6 +914,8 @@ patterns are available:
- `scheme` suitable for source code in most Lisp dialects,
including Scheme, Emacs Lisp, Common Lisp, and Clojure.
+- `swift` suitable for source code in the Swift language.
+
- `tex` suitable for source code for LaTeX documents.
diff --git a/t/t4018/swift-actor b/t/t4018/swift-actor
new file mode 100644
index 0000000000..e4852f40a7
--- /dev/null
+++ b/t/t4018/swift-actor
@@ -0,0 +1,5 @@
+actor RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-attribute-with-args b/t/t4018/swift-attribute-with-args
new file mode 100644
index 0000000000..22b1ee32f1
--- /dev/null
+++ b/t/t4018/swift-attribute-with-args
@@ -0,0 +1,7 @@
+struct View {
+ @available(iOS 13, *) public func RIGHT() {
+ let a = 1
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-class b/t/t4018/swift-class
new file mode 100644
index 0000000000..c3a9336027
--- /dev/null
+++ b/t/t4018/swift-class
@@ -0,0 +1,5 @@
+class RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/t/t4018/swift-enum b/t/t4018/swift-enum
new file mode 100644
index 0000000000..0a84302993
--- /dev/null
+++ b/t/t4018/swift-enum
@@ -0,0 +1,5 @@
+enum RIGHT {
+ case first
+ // a comment
+ case ChangeMe
+}
diff --git a/t/t4018/swift-extension b/t/t4018/swift-extension
new file mode 100644
index 0000000000..cbc18ab6ef
--- /dev/null
+++ b/t/t4018/swift-extension
@@ -0,0 +1,5 @@
+extension RIGHT {
+ static let a = 1
+ // a comment
+ static let b = ChangeMe
+}
diff --git a/t/t4018/swift-failable-init b/t/t4018/swift-failable-init
new file mode 100644
index 0000000000..4bbd6217c9
--- /dev/null
+++ b/t/t4018/swift-failable-init
@@ -0,0 +1,7 @@
+class Bar {
+ init?(RIGHT: Int) {
+ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-func b/t/t4018/swift-func
new file mode 100644
index 0000000000..1fecae0911
--- /dev/null
+++ b/t/t4018/swift-func
@@ -0,0 +1,5 @@
+func RIGHT(x: Int) -> Int {
+ let y = x
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-generic-subscript b/t/t4018/swift-generic-subscript
new file mode 100644
index 0000000000..423cb58941
--- /dev/null
+++ b/t/t4018/swift-generic-subscript
@@ -0,0 +1,7 @@
+struct Container {
+ subscript<RIGHT>(index: Int) -> Int {
+ let a = 0
+ // a comment
+ return ChangeMe
+ }
+}
diff --git a/t/t4018/swift-init b/t/t4018/swift-init
new file mode 100644
index 0000000000..dc7a298f38
--- /dev/null
+++ b/t/t4018/swift-init
@@ -0,0 +1,7 @@
+class Foo {
+ init(RIGHT: Int) {
+ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-inline-attribute b/t/t4018/swift-inline-attribute
new file mode 100644
index 0000000000..2374c4b603
--- /dev/null
+++ b/t/t4018/swift-inline-attribute
@@ -0,0 +1,7 @@
+class Service {
+ @objc func RIGHT() {
+ let path = "/api"
+ // a comment
+ log(ChangeMe)
+ }
+}
diff --git a/t/t4018/swift-modifiers b/t/t4018/swift-modifiers
new file mode 100644
index 0000000000..9d80685a78
--- /dev/null
+++ b/t/t4018/swift-modifiers
@@ -0,0 +1,4 @@
+public static func RIGHT() -> Int {
+ // a comment
+ return ChangeMe
+}
diff --git a/t/t4018/swift-protocol b/t/t4018/swift-protocol
new file mode 100644
index 0000000000..07c39ec2a3
--- /dev/null
+++ b/t/t4018/swift-protocol
@@ -0,0 +1,5 @@
+protocol RIGHT {
+ var first: Int { get }
+ // a comment
+ var second: ChangeMe { get }
+}
diff --git a/t/t4018/swift-struct b/t/t4018/swift-struct
new file mode 100644
index 0000000000..e399ed7759
--- /dev/null
+++ b/t/t4018/swift-struct
@@ -0,0 +1,5 @@
+struct RIGHT {
+ let a = 1
+ // a comment
+ let b = ChangeMe
+}
diff --git a/userdiff.c b/userdiff.c
index b5412e6bc3..7129bf1482 100644
--- a/userdiff.c
+++ b/userdiff.c
@@ -362,6 +362,16 @@ PATTERNS("scheme",
"\\|([^|\\\\]|\\\\.)*\\|"
/* All other words should be delimited by spaces or parentheses. */
"|([^][)(}{ \t])+"),
+PATTERNS("swift",
+ "^[ \t]*((@[A-Za-z_][A-Za-z0-9_]*(\\([^()]*\\))?[ \t]+)*([a-z]+[ \t]+)*(func|init|deinit|subscript|class|struct|enum|protocol|extension|actor)[ \t(?!<].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ /* hexadecimal, octal, and binary literals */
+ "|0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+"
+ /* integers and floating-point numbers */
+ "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
+ /* unary and binary operators */
+ "|[-+*/%<>=!&|^~?]=|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
"\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"),
{ .name = "default", .binary = -1 },
Range-diff against v1:
1: 1e7e199355 ! 1: af48611565 userdiff: add support for Swift
@@ t/t4018/swift-failable-init (new)
@@
+class Bar {
+ init?(RIGHT: Int) {
-+ let value = RIGHT
++ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
@@ t/t4018/swift-func (new)
## t/t4018/swift-generic-subscript (new) ##
@@
+struct Container {
-+ subscript<RIGHT>(index: RIGHT) -> Int {
++ subscript<RIGHT>(index: Int) -> Int {
+ let a = 0
+ // a comment
+ return ChangeMe
@@ t/t4018/swift-init (new)
@@
+class Foo {
+ init(RIGHT: Int) {
-+ let value = RIGHT
++ let x = 0
+ // a comment
+ print(ChangeMe)
+ }
@@ userdiff.c: PATTERNS("scheme",
+ /* integers and floating-point numbers */
+ "|[0-9][0-9_]*([.][0-9_]+)?([eE][-+]?[0-9]+)?"
+ /* unary and binary operators */
-+ "|[-+*/%<>=!&|^~?]=?|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
++ "|[-+*/%<>=!&|^~?]=|&&|\\|\\||<<=?|>>=?|\\?\\?|\\.\\.[.<]|->"),
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
"\\\\[a-zA-Z@]+|\\\\.|([a-zA-Z0-9]|[^\x01-\x7f])+"),
{ .name = "default", .binary = -1 },
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-21 6:57 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 14:02 [PATCH] userdiff: add support for Swift Shlok Kulshreshtha
2026-07-17 16:27 ` Junio C Hamano
2026-07-18 18:11 ` Johannes Sixt
2026-07-20 4:55 ` Junio C Hamano
2026-07-20 8:49 ` Johannes Sixt
2026-07-20 9:52 ` Shlok Kulshreshtha
2026-07-21 6:57 ` [PATCH v2] " Shlok Kulshreshtha
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox