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:
Dmitri Hrybenko
2014-07-24 16:51:48 +00:00
parent 815d09c049
commit a3d5a8a0de
7 changed files with 142 additions and 34 deletions

View File

@@ -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
}
} }

View File

@@ -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>,

View File

@@ -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 }

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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()
} }

View File

@@ -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.