You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[TSCUtility] Correct semantic version parsing and comparison (#214)
The semantic versioning specification 2.0.0 [states](https://semver.org/#spec-item-9) that pre-release identifiers must be positioned after the version core, and build metadata identifiers after pre-release identifiers.
In the old implementation, if a version core was appended with metadata identifiers that contain hyphens ("-"), the first hyphen would be mistaken as an indication of pre-release identifiers thereafter. Then, the position of the first hyphen would be treated as where the version core ends, resulting in a false negative after it was found that the "version core" contained non-numeric characters.
For example: the semantic version `1.2.3+some-meta.data` is a well-formed, with `1.2.3` being the version core and `some-meta.data` the metadata identifiers. However, the old implementation of `Version.init?(_ versionString: String)` would falsely treat `1.2.3+some` as the version core and `meta.data` the pre-release identifiers.
The new implementation fixes this problem by restricting the search area for "-" to the substring before the first "+".
The initialiser wherein the parsing takes place has been renamed from `init?(string: String)` to `init?(_ versionString: String)`. The old initialiser is not removed but marked as deprecated for source compatibility with SwiftPM. With the new initialiser name, `Version` now conforms to `LosslessStringConvertible`.
In addition, the logic for breaking up the version core into numeric identifiers has been rewritten to be more understandable.
`Comparable` does not provide a default implementation for `==`, so the compiler synthesises one composed of [member-wise comparisons](https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#implementation-details). This leads to a false `false` when 2 semantic versions differ by only their build metadata identifiers, contradicting to SemVer 2.0.0's [comparison rules](https://semver.org/#spec-item-10).
This commit adds a manual implementation of `==` for `Version`, along with appropriate tests. One consequence, though, is that now two versions that differ by only their build metadata identifiers are not allowed in the same set.
Because we have a non-synthesised `Equatable` conformance, the synthesised `Hashable` conformance composed of member-wise hashes is incorrect. `buildMetadataIdentifiers` does not participate in `Version`'s `Equatable` conformance, so it shouldn't participate in `Version`'s `Hashable` conformance either.
Relevant: [SR-11588](https://bugs.swift.org/browse/SR-11588)
other changes:
* add additional tests for initialising `Version`
* rearranged the tests.
* [garderning] fix typo "ranage" → "range"
* add a throwing initialiser for creating a version from a string
This new initialiser throws a `VersionError` instance when initialisation fails. This gives the user more information and control over error handling. `Version`'s conformance to `LosslessStringConvertible` is preserved by having `init?(_ versionString: String)` call this new initialiser, and return `nil` when an error is thrown.
* [gardening] remove horizontal whitespace from whitespace-only lines
return"non-numerical characters in version core identifier\(nonNumericalIdentifiers.count >1?"s":"")\(nonNumericalIdentifiers.map{"'\($0)'"}.joined(separator:", "))"
return"characters other than alpha-numerics and hyphens in pre-release identifier\(nonAlphaNumericalIdentifiers.count >1?"s":"")\(nonAlphaNumericalIdentifiers.map{"'\($0)'"}.joined(separator:", "))"
return"characters other than alpha-numerics and hyphens in build metadata identifier\(nonAlphaNumericalIdentifiers.count >1?"s":"")\(nonAlphaNumericalIdentifiers.map{"'\($0)'"}.joined(separator:", "))"
83
+
}
84
+
}
85
+
}
86
+
87
+
extensionVersion{
88
+
// TODO: Rename this function to `init(string: String) throws`, after `init?(string: String)` is removed.
89
+
// TODO: Find a better error-checking order.
90
+
// Currently, if a version string is "forty-two", this initializer throws an error that says "forty" is only 1 version core identifier, which is not enough.
91
+
// But this is misleading the user to consider "forty" as a valid version core identifier.
92
+
// We should find a way to check for (or throw) "wrong characters used" errors first, but without overly-complicating the logic.
93
+
/// Creates a version from the given string.
94
+
/// - Parameter versionString: The string to create the version from.
95
+
/// - Throws: A `VersionError` instance if the `versionString` doesn't follow [SemVer 2.0.0](https://semver.org).
96
+
publicinit(versionString:String)throws{
97
+
// SemVer 2.0.0 allows only ASCII alphanumerical characters and "-" in the version string, except for "." and "+" as delimiters. ("-" is used as a delimiter between the version core and pre-release identifiers, but it's allowed within pre-release and metadata identifiers as well.)
98
+
// Alphanumerics check will come later, after each identifier is split out (i.e. after the delimiters are removed).
return major == other.major && minor == other.minor && patch == other.patch
52
156
}
53
157
158
+
// Although `Comparable` inherits from `Equatable`, it does not provide a new default implementation of `==`, but instead uses `Equatable`'s default synthesised implementation. The compiler-synthesised `==`` is composed of [member-wise comparisons](https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#implementation-details), which leads to a false `false` when 2 semantic versions differ by only their build metadata identifiers, contradicting SemVer 2.0.0's [comparison rules](https://semver.org/#spec-item-10).
0 commit comments