New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-10312] Trap if using an Index from another String #52712
Comments
Here, text and nsString are distinct strings and you cannot use indices across different strings. You'll need to use (nsString as String).range(of: "04:54AM")!. Let me throw together a test file to make sure we have the right behavior, though. |
Comment by Raphael Cruzeiro (JIRA) @milseman I figured it would be something like that and it's possible that this behaviour won't be an issue for most people. The problem, in my opinion, arises when you throw in UIKit into the mix. I first encountered this issue in a helper extension to add specific styling to ranges of text on a UILabel. Since (as far as the developer is concerned), the `text` attribute of a `UILabel` is `String`, it is to expect that if both strings have the same set of characters as a value, they are equivalent. Here is the extension that broke under Swift 5 with the workaround already in place extension UILabel {
func add(style: TextStyle, to range: Range<String.Index>) {
let string: String = "\(self.text ?? "")"
let nsRange = NSRange(range, in: string)
let attributedString = NSMutableAttributedString(attributedString: self.attributedText!)
attributedString.addAttributes([
.foregroundColor: style.foregroundColor,
.font: style.font
], range: nsRange)
if style.strikeThrough {
attributedString.addAttributes([
.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue),
.strikethroughColor: style.foregroundColor
],
range: nsRange
)
}
self.attributedText = attributedString
}
} |
I understand that expectation, but it also sort of depends on what you mean by "same set of characters". By characters, if you mean Character, then no, as
either as String or Character. If you mean Unicode scalar values, then it does depend on whether representation is relevant to whether they are the "same set". Would it be useful to have a String API that's something to the effect of
Or maybe
As for NSAttributedString usage, CC @itaiferber. Itai, do you have any ideas how to write this better? |
Comment by Raphael Cruzeiro (JIRA) Another point of confusion here is that: text == NSString as String Which I guess brings the question of what should define the equivalence of two strings (not arguing that they would be !=). For developers that started coding with Swift and have never touched Obj-C, an error like this would be really confusing as they won't be aware that UIKit deals with NSStrings internally and that what you get back might not necessarily be what you passed to it. |
Comment by Raphael Cruzeiro (JIRA) Perhaps the only real solution for this is to make this kind of behaviour extra clear in the docs (and hope UIKit follows suit). |
This falls under the category of not being able to use indices with collections they didn't originally come from. For example, Dictionary will actually trap if you use an index from a different dictionary, and we've had long-standing requests to do the same for String. We have some bits reserved for this purpose and might add it in the future. Do you want to move this to a documentation request bug instead? CC @natecook1000 for ideas regarding documentation. |
Comment by Raphael Cruzeiro (JIRA) @milseman, having the same trap for String would actually make sense. It would make this kind of mistake really obvious. With Swift 4.2 you could use the same indices with different strings and it would work 100% of the time (even if that was not intended). Now with Swift 5, this will still work on quite a lot of cases (it only breaks if you bridge from NSString and you have characters that might end up getting encoded differently). I believe that making the string trap would be the most efficient way to make it clear that it's mot supposed to be used that way. |
Yes, I've been thinking of what time to introduce this trap. We'd probably want to roll it out initially as a debug-only precondition failure (preserve runtime behavior of shipping code), which would also allow us to keep it's representation opaque at the ABI level. I was considering whether it's better to wait for more ease-of-use functionality in String, since currently many users have to drop down to the indexing level for common operations. Do you find yourself manipulating String.Index in your own code, or do you mostly use it (and ranges) as arguments and return values? edit: grammar |
I'm converting this bug to track the trapping feature |
Additional Detail from JIRA
md5: eea8c7490c7ace945740627cc99a3cce
Issue Description:
Resolution: We should trap (perhaps debug only) when using an index from a different String than it was created from.
Original bug:
Generating a NSRange from a Range<String.Index> is giving inconsistent results if the string was bridged from NSString. I believe this is because Swift 5 is using UTF-8 whereas NSString uses UTF-16.
The following code demonstrates the issue:
The above code runs fine on Swift 4.2 but generates inconsistent results on Swift 5. On a real world scenario, the first NSRange could be generated by using the text property of a UILabel crashing the app when the range is used as it's upper bound will be out of the string ranges.
The text was updated successfully, but these errors were encountered: