mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
stdlib/String: change == to perform string comparison after NFD
normalization There is still some obscure bug with != on NSString, probably caused by an ill-thought overload somewhere. Part of rdar://17498444 Swift SVN r20495
This commit is contained in:
@@ -121,15 +121,8 @@ extension String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func ==(lhs: Character, rhs: Character) -> Bool {
|
public func ==(lhs: Character, rhs: Character) -> Bool {
|
||||||
switch (lhs, rhs) {
|
// FIXME(performance): constructing two temporary strings is extremely
|
||||||
case (.LargeRepresentation(let lhsValue), .LargeRepresentation(let rhsValue)):
|
// wasteful and inefficient.
|
||||||
return lhsValue._value == rhsValue._value
|
return String(lhs) == String(rhs)
|
||||||
|
|
||||||
case (.SmallRepresentation(let lhsValue), .SmallRepresentation(let rhsValue)):
|
|
||||||
return Character._smallValue(lhsValue) == Character._smallValue(rhsValue)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ public func _stdlib_getTypeName<T>(value: T) -> String {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the human-readable type name for the given value.
|
||||||
|
public func _stdlib_getDemangledTypeName<T>(value: T) -> String {
|
||||||
|
return _stdlib_demangleName(_stdlib_getTypeName(value))
|
||||||
|
}
|
||||||
|
|
||||||
@asmname("swift_stdlib_demangleName")
|
@asmname("swift_stdlib_demangleName")
|
||||||
func _stdlib_demangleNameImpl(
|
func _stdlib_demangleNameImpl(
|
||||||
mangledName: UnsafePointer<UInt8>,
|
mangledName: UnsafePointer<UInt8>,
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ struct _StructMirror: MirrorType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var summary: String {
|
var summary: String {
|
||||||
return _stdlib_demangleName(_stdlib_getTypeName(value))
|
return _stdlib_getDemangledTypeName(value)
|
||||||
}
|
}
|
||||||
var quickLookObject: QuickLookObject? { return nil }
|
var quickLookObject: QuickLookObject? { return nil }
|
||||||
var disposition: MirrorDisposition { return .Struct }
|
var disposition: MirrorDisposition { return .Struct }
|
||||||
@@ -421,7 +421,7 @@ struct _ClassMirror: MirrorType {
|
|||||||
return _getClassChild(i, data)
|
return _getClassChild(i, data)
|
||||||
}
|
}
|
||||||
var summary: String {
|
var summary: String {
|
||||||
return _stdlib_demangleName(_stdlib_getTypeName(value))
|
return _stdlib_getDemangledTypeName(value)
|
||||||
}
|
}
|
||||||
var quickLookObject: QuickLookObject? {
|
var quickLookObject: QuickLookObject? {
|
||||||
return _getClassQuickLookObject(data)
|
return _getClassQuickLookObject(data)
|
||||||
@@ -446,7 +446,7 @@ struct _ClassSuperMirror: MirrorType {
|
|||||||
return _getClassChild(i, data)
|
return _getClassChild(i, data)
|
||||||
}
|
}
|
||||||
var summary: String {
|
var summary: String {
|
||||||
return _stdlib_demangleName(_stdlib_getTypeName(value))
|
return _stdlib_getDemangledTypeName(value)
|
||||||
}
|
}
|
||||||
var quickLookObject: QuickLookObject? { return nil }
|
var quickLookObject: QuickLookObject? { return nil }
|
||||||
var disposition: MirrorDisposition { return .Class }
|
var disposition: MirrorDisposition { return .Class }
|
||||||
|
|||||||
@@ -173,19 +173,26 @@ extension String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compare two strings, normalizing them to NFD first.
|
||||||
|
///
|
||||||
|
/// The behavior is equivalent to `NSString.compare()` with default options.
|
||||||
|
///
|
||||||
|
/// :returns:
|
||||||
|
/// * -1 if `lhs < rhs`,
|
||||||
|
/// * 0 if `lhs == rhs`,
|
||||||
|
/// * 1 if `lhs > rhs`.
|
||||||
|
@asmname("swift_stdlib_compareNSStringNormalizingToNFD")
|
||||||
|
func _stdlib_compareNSStringNormalizingToNFD(lhs: AnyObject, rhs: AnyObject)
|
||||||
|
-> Int
|
||||||
|
|
||||||
extension String: Equatable {
|
extension String: Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ==(lhs: String, rhs: String) -> Bool {
|
public func ==(lhs: String, rhs: String) -> Bool {
|
||||||
// FIXME: Compares UnicodeScalars, but should eventually do proper
|
// Note: this operation should be consistent with equality comparison of
|
||||||
// Unicode string comparison. This is above the level of the
|
// Character.
|
||||||
// standard equal algorithm because even the largest units
|
return _stdlib_compareNSStringNormalizingToNFD(
|
||||||
// (Characters/a.k.a. grapheme clusters) don't have a 1-for-1
|
lhs._bridgeToObjectiveCImpl(), rhs._bridgeToObjectiveCImpl()) == 0
|
||||||
// correspondence. For example, "SS" == "ß" should be true.
|
|
||||||
//
|
|
||||||
// NOTE: if this algorithm is changed, consider updating equality comparison
|
|
||||||
// of Character.
|
|
||||||
return Swift.equal(lhs.unicodeScalars, rhs.unicodeScalars)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func <(lhs: String, rhs: String) -> Bool {
|
public func <(lhs: String, rhs: String) -> Bool {
|
||||||
|
|||||||
@@ -93,6 +93,16 @@ extern "C" bool swift_stdlib_NSObject_isEqual(NSObject *lhs, NSObject *rhs) {
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" int swift_stdlib_compareNSStringNormalizingToNFD(NSString *lhs,
|
||||||
|
NSString *rhs) {
|
||||||
|
// 'kCFCompareNonliteral' actually means "normalize to NFD".
|
||||||
|
int Result = CFStringCompare((__bridge CFStringRef)lhs,
|
||||||
|
(__bridge CFStringRef)rhs, kCFCompareNonliteral);
|
||||||
|
swift_unknownRelease(lhs);
|
||||||
|
swift_unknownRelease(rhs);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
// ${'Local Variables'}:
|
// ${'Local Variables'}:
|
||||||
// eval: (read-only-mode 1)
|
// eval: (read-only-mode 1)
|
||||||
// End:
|
// End:
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ public func expectEqual<T>(
|
|||||||
_anyExpectFailed = true
|
_anyExpectFailed = true
|
||||||
println("check failed at \(file), line \(line)")
|
println("check failed at \(file), line \(line)")
|
||||||
_printStackTrace(stackTrace)
|
_printStackTrace(stackTrace)
|
||||||
println("expected: \"\(expected)\" (of type \(_stdlib_getTypeName(expected)))")
|
println("expected: \"\(expected)\" (of type \(_stdlib_getDemangledTypeName(expected)))")
|
||||||
println("actual: \"\(actual)\" (of type \(_stdlib_getTypeName(expected)))")
|
println("actual: \"\(actual)\" (of type \(_stdlib_getDemangledTypeName(expected)))")
|
||||||
if collectMoreInfo { println(collectMoreInfo!()) }
|
if collectMoreInfo { println(collectMoreInfo!()) }
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
@@ -107,12 +107,14 @@ public func expectEqual<T>(
|
|||||||
|
|
||||||
public func expectNotEqual<T : Equatable>(
|
public func expectNotEqual<T : Equatable>(
|
||||||
expected: T, actual: T,
|
expected: T, actual: T,
|
||||||
|
stackTrace: SourceLocStack? = nil,
|
||||||
file: String = __FILE__, line: UWord = __LINE__
|
file: String = __FILE__, line: UWord = __LINE__
|
||||||
) {
|
) {
|
||||||
if expected == actual {
|
if expected == actual {
|
||||||
_anyExpectFailed = true
|
_anyExpectFailed = true
|
||||||
println("check failed at \(file), line \(line)")
|
println("check failed at \(file), line \(line)")
|
||||||
println("unexpected value: \"\(actual)\" (of type \(_stdlib_getTypeName(actual)))")
|
_printStackTrace(stackTrace)
|
||||||
|
println("unexpected value: \"\(actual)\" (of type \(_stdlib_getDemangledTypeName(actual)))")
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,8 +128,8 @@ public func expectOptionalEqual<T : Equatable>(
|
|||||||
if !actual || expected != actual! {
|
if !actual || expected != actual! {
|
||||||
_anyExpectFailed = true
|
_anyExpectFailed = true
|
||||||
println("check failed at \(file), line \(line)")
|
println("check failed at \(file), line \(line)")
|
||||||
println("expected: \"\(expected)\" (of type \(_stdlib_getTypeName(expected)))")
|
println("expected: \"\(expected)\" (of type \(_stdlib_getDemangledTypeName(expected)))")
|
||||||
println("actual: \"\(actual)\" (of type \(_stdlib_getTypeName(actual)))")
|
println("actual: \"\(actual)\" (of type \(_stdlib_getDemangledTypeName(actual)))")
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +166,7 @@ func _expectNotEqual${Generic}(
|
|||||||
if expected == actual {
|
if expected == actual {
|
||||||
_anyExpectFailed = true
|
_anyExpectFailed = true
|
||||||
println("check failed at \(file), line \(line)")
|
println("check failed at \(file), line \(line)")
|
||||||
println("unexpected value: \"\(actual)\" (of type \(_stdlib_getTypeName(actual)))")
|
println("unexpected value: \"\(actual)\" (of type \(_stdlib_getDemangledTypeName(actual)))")
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,7 +482,7 @@ public func expectEqualSequence<
|
|||||||
println("check failed at \(file), line \(line)")
|
println("check failed at \(file), line \(line)")
|
||||||
_printStackTrace(stackTrace)
|
_printStackTrace(stackTrace)
|
||||||
println("expected elements: \"\(expected)\"")
|
println("expected elements: \"\(expected)\"")
|
||||||
println("actual: \"\(actual)\" (of type \(_stdlib_getTypeName(actual)))")
|
println("actual: \"\(actual)\" (of type \(_stdlib_getDemangledTypeName(actual)))")
|
||||||
if collectMoreInfo { println(collectMoreInfo!()) }
|
if collectMoreInfo { println(collectMoreInfo!()) }
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1138,12 +1138,103 @@ NSStringAPIs.test("writeToURL(_:atomically:encoding:error:)") {
|
|||||||
// FIXME
|
// FIXME
|
||||||
}
|
}
|
||||||
|
|
||||||
NSStringAPIs.test("OperatorEquals") {
|
func checkEqualityImpl(
|
||||||
// FIXME
|
expected: Bool, lhs: String, rhs: String,
|
||||||
|
stackTrace: SourceLocStack
|
||||||
|
) {
|
||||||
|
// String / String
|
||||||
|
expectEqual(expected, lhs == rhs, stackTrace: stackTrace)
|
||||||
|
expectEqual(!expected, lhs != rhs, stackTrace: stackTrace)
|
||||||
|
|
||||||
// NSString == NSString
|
// NSString / NSString
|
||||||
// String == NSString
|
let lhsNSString = lhs as NSString
|
||||||
// NSString == String
|
let rhsNSString = rhs as NSString
|
||||||
|
expectEqual(expected, lhsNSString == rhsNSString, stackTrace: stackTrace)
|
||||||
|
// FIXME: this fails now.
|
||||||
|
//expectEqual(!expected, lhsNSString != rhsNSString, stackTrace: stackTrace)
|
||||||
|
|
||||||
|
// String / NSString
|
||||||
|
expectEqual(expected, lhs == rhsNSString, stackTrace: stackTrace)
|
||||||
|
expectEqual(!expected, lhs != rhsNSString, stackTrace: stackTrace)
|
||||||
|
|
||||||
|
// NSString / String
|
||||||
|
expectEqual(expected, lhs == rhsNSString, stackTrace: stackTrace)
|
||||||
|
expectEqual(!expected, lhs != rhsNSString, stackTrace: stackTrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkEquality(
|
||||||
|
expected: Bool, lhs: String, rhs: String, stackTrace: SourceLocStack
|
||||||
|
) {
|
||||||
|
checkEqualityImpl(expected, lhs, rhs, stackTrace.withCurrentLoc())
|
||||||
|
checkEqualityImpl(expected, rhs, lhs, stackTrace.withCurrentLoc())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EqualityTest {
|
||||||
|
let expected: Bool
|
||||||
|
let lhs: String
|
||||||
|
let rhs: String
|
||||||
|
let loc: SourceLoc
|
||||||
|
|
||||||
|
init(_ expected: Bool, _ lhs: String, _ rhs: String,
|
||||||
|
file: String = __FILE__, line: UWord = __LINE__) {
|
||||||
|
self.expected = expected
|
||||||
|
self.lhs = lhs
|
||||||
|
self.rhs = rhs
|
||||||
|
self.loc = SourceLoc(file, line, comment: "test data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let equalityTests = [
|
||||||
|
EqualityTest(true, "", ""),
|
||||||
|
EqualityTest(false, "a", ""),
|
||||||
|
|
||||||
|
// U+0301 COMBINING ACUTE ACCENT
|
||||||
|
// U+00E1 LATIN SMALL LETTER A WITH ACUTE
|
||||||
|
EqualityTest(true, "a\u{301}", "\u{e1}"),
|
||||||
|
EqualityTest(false, "a\u{301}", "a"),
|
||||||
|
EqualityTest(false, "\u{e1}", "a"),
|
||||||
|
|
||||||
|
// U+304B HIRAGANA LETTER KA
|
||||||
|
// U+304C HIRAGANA LETTER GA
|
||||||
|
// U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK
|
||||||
|
EqualityTest(true, "\u{304b}", "\u{304b}"),
|
||||||
|
EqualityTest(true, "\u{304c}", "\u{304c}"),
|
||||||
|
EqualityTest(false, "\u{304b}", "\u{304c}"),
|
||||||
|
EqualityTest(false, "\u{304b}", "\u{304c}\u{3099}"),
|
||||||
|
EqualityTest(true, "\u{304c}", "\u{304b}\u{3099}"),
|
||||||
|
EqualityTest(false, "\u{304c}", "\u{304c}\u{3099}"),
|
||||||
|
|
||||||
|
// U+212B ANGSTROM SIGN
|
||||||
|
// U+030A COMBINING RING ABOVE
|
||||||
|
// U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE
|
||||||
|
EqualityTest(true, "\u{212b}", "A\u{30a}"),
|
||||||
|
EqualityTest(true, "\u{212b}", "\u{c5}"),
|
||||||
|
EqualityTest(true, "A\u{30a}", "\u{c5}"),
|
||||||
|
EqualityTest(false, "A\u{30a}", "a"),
|
||||||
|
|
||||||
|
// U+2126 OHM SIGN
|
||||||
|
// U+03A9 GREEK CAPITAL LETTER OMEGA
|
||||||
|
EqualityTest(true, "\u{2126}", "\u{03a9}"),
|
||||||
|
|
||||||
|
// U+1E69 LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
|
||||||
|
// U+0323 COMBINING DOT BELOW
|
||||||
|
// U+0307 COMBINING DOT ABOVE
|
||||||
|
// U+1E63 LATIN SMALL LETTER S WITH DOT BELOW
|
||||||
|
EqualityTest(true, "\u{1e69}", "s\u{323}\u{307}"),
|
||||||
|
EqualityTest(true, "\u{1e69}", "s\u{307}\u{323}"),
|
||||||
|
EqualityTest(true, "\u{1e69}", "\u{1e63}\u{307}"),
|
||||||
|
EqualityTest(true, "\u{1e63}\u{307}", "s\u{323}\u{307}"),
|
||||||
|
EqualityTest(true, "\u{1e63}\u{307}", "s\u{307}\u{323}"),
|
||||||
|
|
||||||
|
// U+FB01 LATIN SMALL LIGATURE FI
|
||||||
|
EqualityTest(true, "\u{fb01}", "\u{fb01}"),
|
||||||
|
EqualityTest(false, "\u{fb01}", "fi"),
|
||||||
|
]
|
||||||
|
|
||||||
|
NSStringAPIs.test("OperatorEquals") {
|
||||||
|
for test in equalityTests {
|
||||||
|
checkEquality(test.expected, test.lhs, test.rhs, test.loc.withCurrentLoc())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: these properties should be implemented in the core library.
|
// FIXME: these properties should be implemented in the core library.
|
||||||
|
|||||||
Reference in New Issue
Block a user