Skip to content

Commit ef0b101

Browse files
committed
Proofing
1 parent 40c2321 commit ef0b101

File tree

1 file changed

+82
-29
lines changed

1 file changed

+82
-29
lines changed

proposals/0243-character-operators.md

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,159 @@
1-
# Character Literal Opertaors
1+
# Character Literal Operators
22

33
* Proposal: [SE-0243](0243-character-operators.md)
44
* Authors: [Dianna ma (“Taylor Swift”)](https://github.com/kelvin13), [John Holdsworth](https://github.com/johnno1962)
55
* Review manager: [Ben Cohen](https://github.com/airspeedswift)
66
* Status: **Second review**
7-
* Implementation: [apple/swift#NNN](https://github.com/apple/swift/compare/main...johnno1962:swift:character-ops?expand=1)
7+
* Implementation: [apple/swift#71749](https://github.com/apple/swift/pull/71749)
88
* Threads: [1](https://forums.swift.org/t/prepitch-character-integer-literals/10442) [2](https://forums.swift.org/t/se-0243-codepoint-and-character-literals/21188) [3](https://forums.swift.org/t/single-quoted-character-literals-why-yes-again/61898)
99

1010
## Introduction
1111

12-
This proposal reboots efforts to improve the ergonomics of the Swift language for a class of code involved in parsing, for example JSON or the Swift language itself. Whereas previously it was thought a single quoted syntax for these literals could be pressed into service alongside integer express-ability it was realised that adding few well chosen operators to the standard library could serve the most pressing use cases. That this works and is performant has been demonstrated in [this PR](https://github.com/apple/swift-syntax/pull/2439#issuecomment-1922292277) to the `swift-syntax` library where the readability of code was increased with an ever so slight improvement in performance.
12+
This proposal improves Swift's character-literal ergonomics. This support is fundamental not only to parsing tasks within the Swift language but also to tasks that require developers to extract and manipulate data. Areas that would benefit include handling domain-specific languages (DSLs) and parsing commonly-used data formats such as JSON. Any workflow based on lexical analysis or tokenization requirements will gain from this proposal.
13+
14+
The Swift community previously considered single-quote syntax for character literals. While working on Swift's Lexer code, another solution came to light. Adding well-chosen operators to the Standard Library tidied up the Lexer implementation with minimal impact on the language. These operators didn't burn the single-quote for future reserved use, they served all the most pressing use-cases effectively and demonstrated small but measurable performance improvements.
15+
16+
This improvement was validated through our work on [PR 2439](https://github.com/apple/swift-syntax/pull/2439#issuecomment-1922292277). The patch showcased how to streamline character-binary integer interchange for low level code. This proposal offers the same readable solution that seamlessly integrates with the established character and style of Swift. Additionally, it provides a slight performance boost, making it a valuable enhancement for performant code.
1317

1418
## Motivation
1519

16-
At present, the rather cumbersome constructs `UInt8(ascii: "c")` or perhaps `UnicadeScalar("c").value` are the only interface to the binary integer equivalent of `unicode scalars` in the Swift language. Data is not always UInt8 or UInt32 however so, frequently these have to be combined with a cast and overall the ergonomics are sub-optimal. For example, when wanting to `switch` over a range of values as in the previous version of the lexer code cluttered with `UInt8(ascii: "x")` in the PR mentioned above.
20+
Swift's existing character-literal constructs are hard to read and an effort to construct. Contorted expressions like `UInt8(ascii: "c")` and `UnicodeScalar("c").value` provide Swift's current entry points to the binary integer equivalent of unicode scalars.
21+
22+
Since `Data` is not always `UInt8` or `UInt32`, these frequently must be combined with casts. User ergonomics are crying out for improvement. Consider the previous version of our lexer code. To `switch` over a range of values, our implementation in [PR 2439](https://github.com/apple/swift-syntax/pull/2439#issuecomment-1922292277) was cluttered with ergonomically unsound expressions like the previously mentioned `UInt8(ascii: "x")`.
23+
24+
Swift deserves better.
1725

18-
Swift allows you to define operators for equivalence and also for the pattern matching used in `switch` statements. It is sufficient therefore to add binary operators to allow direct comparisons between integer types and unicode scalars. This approach has been shown to be perfectly performant and effectively "compiles down to the same thing".
26+
Our proposed change has precedent. Swift allows you to define custom operators for both equivalence and the pattern matching used in `switch` statements and elsewhere. Adding binary operators allows direct comparisons between `Integer` types and Unicode scalars. This approach effectively compiles a more readable solution to the same results.
1927

2028
## Proposed solution
2129

22-
Specifically, this proposal puts forward that the following code be added to the file: stdlib/public/core/UnicodeScalar.swift in the standard library:
30+
We propose to introduce the following code to "stdlib/public/core/UnicodeScalar.swift" in the Swift standard library:
2331

2432
```Swift
25-
/// Allows direct comparisons between UInt8 and double quoted literals.
33+
/// Extends `UInt8` to allow direct comparisons with double quoted literals.
2634
extension UInt8 {
27-
/// Basic equality operator
35+
/// Returns a Boolean indicating whether the `UInt8` is equal to the provided Unicode scalar.
36+
///
37+
/// - Parameters:
38+
/// - i: The `UInt8` value to compare.
39+
/// - s: The Unicode scalar to compare against.
40+
/// - Returns: `true` when the `UInt8` is equal to the provided Unicode scalar; otherwise, `false`.
2841
@_transparent @_alwaysEmitIntoClient
2942
public static func == (i: Self, s: Unicode.Scalar) -> Bool {
3043
return i == UInt8(ascii: s)
3144
}
32-
/// Basic inequality operator
45+
46+
/// Returns a Boolean indicating whether the `UInt8` is not equal to the provided Unicode scalar.
47+
///
48+
/// - Parameters:
49+
/// - i: The `UInt8` value to compare.
50+
/// - s: The Unicode scalar to compare against.
51+
/// - Returns: `true` if the `UInt8` is not equal to the provided Unicode scalar; otherwise, `false`.
3352
@_transparent @_alwaysEmitIntoClient
3453
public static func != (i: Self, s: Unicode.Scalar) -> Bool {
3554
return i != UInt8(ascii: s)
3655
}
37-
/// Used in switch statements
56+
57+
/// Enables pattern matching of Unicode scalars in switch statements.
58+
///
59+
/// - Parameters:
60+
/// - s: The Unicode scalar to match.
61+
/// - i: The `UInt8` value to match against.
62+
/// - Returns: `true` if the Unicode scalar matches the `UInt8` value; otherwise, `false`.
3863
@_transparent @_alwaysEmitIntoClient
3964
public static func ~= (s: Unicode.Scalar, i: Self) -> Bool {
4065
return i == UInt8(ascii: s)
4166
}
4267
}
4368

69+
/// Extends `Optional<UInt8>` to allow direct comparisons with double quoted literals.
4470
extension UInt8? {
45-
/// Optional equality operator
71+
/// Returns a Boolean value indicating whether the optional `UInt8` is equal to the provided Unicode scalar.
72+
///
73+
/// - Parameters:
74+
/// - i: The optional `UInt8` value to compare.
75+
/// - s: The Unicode scalar to compare against.
76+
/// - Returns: `true` if the optional `UInt8` is equal to the provided Unicode scalar; otherwise, `false`.
4677
@_transparent @_alwaysEmitIntoClient
4778
public static func == (i: Self, s: Unicode.Scalar) -> Bool {
4879
return i == UInt8(ascii: s)
4980
}
50-
/// Optional inequality operator
81+
82+
/// Returns a Boolean value indicating whether the optional `UInt8` is not equal to the provided Unicode scalar.
83+
///
84+
/// - Parameters:
85+
/// - i: The optional `UInt8` value to compare.
86+
/// - s: The Unicode scalar to compare against.
87+
/// - Returns: `true` if the optional `UInt8` is not equal to the provided Unicode scalar; otherwise, `false`.
5188
@_transparent @_alwaysEmitIntoClient
5289
public static func != (i: Self, s: Unicode.Scalar) -> Bool {
5390
return i != UInt8(ascii: s)
5491
}
55-
/// Used in switch statements
92+
93+
/// Allows pattern matching of Unicode scalars in switch statements.
94+
///
95+
/// - Parameters:
96+
/// - s: The Unicode scalar to match.
97+
/// - i: The optional `UInt8` value to match against.
98+
/// - Returns: `true` if the Unicode scalar matches the optional `UInt8` value; otherwise, `false`.
5699
@_transparent @_alwaysEmitIntoClient
57100
public static func ~= (s: Unicode.Scalar, i: Self) -> Bool {
58101
return i == UInt8(ascii: s)
59102
}
60103
}
61104

105+
/// Extends `Array` where Element is a FixedWidthInteger, providing initialization from a string of Unicode scalars.
62106
extension Array where Element: FixedWidthInteger {
63-
/// Initialise an Integer array with "unicode scalars"
64-
@inlinable @_alwaysEmitIntoClient @_unavailableInEmbedded
65-
public init(scalars: String) {
66-
self.init(scalars.unicodeScalars.map { Element(unicode: $0) })
67-
}
107+
/// Initializes an array of Integers with Unicode scalars represented by the provided string.
108+
///
109+
/// - Parameter scalars: A string containing Unicode scalars.
110+
@inlinable @_alwaysEmitIntoClient @_unavailableInEmbedded
111+
public init(scalars: String) {
112+
self.init(scalars.unicodeScalars.map { Element(unicode: $0) })
113+
}
68114
}
69115

116+
/// Extends `FixedWidthInteger` providing initialization from a Unicode scalar.
70117
extension FixedWidthInteger {
71-
/// Construct with value `v.value`.
118+
/// Initializes a FixedWidthInteger with the value of the provided Unicode scalar.
119+
///
120+
/// - Parameter unicode: The Unicode scalar to initialize from.
121+
/// - Note: Construct with value `v.value`.
72122
@inlinable @_alwaysEmitIntoClient
73123
public init(unicode v: Unicode.Scalar) {
74124
_precondition(v.value <= Self.max,
75-
"Code point value does not fit into type")
125+
"Code point value does not fit into type")
76126
self = Self(v.value)
77127
}
78128
}
79129
```
80130

81-
The last initialiser can be considered optional but could be considered as an alternative to the existing `IntX(UncodeScalar("c").value)` incantation people are expected to discover at the moment for non-ascii code points.
131+
This last initializer is optional. It provides an alternate to the existing `IntX(UncodeScalar("c").value)` incantation currently needed for non-ASCII code points.
82132

83133
## Source compatibility
84134

85-
The operators proposed are additive and after running the existing test suite the net effect seems to be to change the diagnostics given on some pattern matching code which was invalid anyway. The last initialiser proposed can affect what is currently valid code such as the following:
135+
Our proposed operator suite is additive. After running the existing test suite, it does change diagnostics on a limited part of pattern matching code. We believe this diagnostic information was already flawed, and the change inconsequential. Finally, the last initializer (the one we noted as optional) may affect currently valid code, such as the following:
86136

87-
```Swift
137+
```
88138
unicodeScalars.map(UInt32.init)
89-
`
90-
Becomes ambiguous and needs to be rewritten explicitly as:
139+
```
91140

92-
```Swift
141+
Upon adoption, this becomes ambiguous and will need to be rewritten explicitly as:
142+
143+
```
93144
unicodeScalars.map { UInt32($0) }
94145
```
95146

96147
## Effect on ABI stability
97148

98-
The new operator have been annotated with `@_alwaysEmitIntoClient` so any code using them will back-port to versions of the Swift runtime before these operators were added.
149+
Each new operator has been annotated with `@_alwaysEmitIntoClient`. Any code that adopts these operators will back-port to versions of the Swift runtime before these operators were added.
99150

100151
## Effect on API resilience
101152

102-
The operators are straightforward and it is not anticipated they would need to evolve their ABI.
153+
The operators are simple and focused. We don't anticipate the need to evolve their ABI.
103154

104155
## Alternatives considered
105156

106-
There is a long history to the proposal and this is a much scaled back version with less collateral impact on the language than previously reviewed proposals which still satisfy the main use cases. It uses the features of the language rather than changing the language itself. One could argue that users would still be able to define these operators themselves but in the end it is a question of whether this would be a battery sufficiently useful to be included in the standard library. Including them would help discovery as something that people might have previously expected to work coming from another language would "simply work".
157+
This proposal emerges from a history of consideration. This scaled-back proposal presents less collateral impact on the language than previously reviewed proposals. At the same time, it satisfies the most important use cases.
158+
159+
Our proposed approach embraces Swift's existing language features rather than changing the language to reach its solution. We believe this enhancement is sufficiently useful to merit inclusion in the Standard Library. It will support and improve Swift's tooling and provide better ergonomics for Swift's user base without forcing Swift adopters to write their own solutions. Its inclusion will promote discovery, providing a feature that people might have expected to "simply work".

0 commit comments

Comments
 (0)