Skip to content
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

Swift 5: adopt new Data.withUnsafeBytes API #843

Merged
merged 1 commit into from Apr 5, 2019
Merged

Swift 5: adopt new Data.withUnsafeBytes API #843

merged 1 commit into from Apr 5, 2019

Conversation

alanzeino
Copy link
Contributor

Unfortunately I wasn't able to use the #if compiler(>=5.0) flag since the project supports older swift versions where this directive is not available.

@thomasvl
Copy link
Collaborator

thomasvl commented Mar 16, 2019

Unfortunately I wasn't able to use the #if compiler(>=5.0) flag since the project supports older swift versions where this directive is not available.

Why? We already use directive like that without problems.

Copy link
Collaborator

@thomasvl thomasvl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather that duplicating some of the larger blocks of code, can any of this be down by providing shims for for older Swift version that expose the new api names instead? Like:

https://github.com/apple/swift-protobuf/blob/master/Sources/SwiftProtobuf/MathUtils.swift#L42-L88

This way the code gets update to the Swift 5 syntax and the conditionals are kept to a minium reducing the chance of a bug that can exist in two almost identical blocks.

@alanzeino
Copy link
Contributor Author

alanzeino commented Mar 19, 2019

Unfortunately I wasn't able to use the #if compiler(>=5.0) flag since the project supports older swift versions where this directive is not available.

Why? We already use directive like that without problems.

Sorry I should've been more clear. #if swift() has been supported for a while but #if compiler() less so. The project at first glance supports Swift versions right back to 3.0, but #if compiler() was added much later than that.

I didn't see any in the codebase when I grep'd for it, but I would prefer to be wrong because I want to use that here :)

Rather that duplicating some of the larger blocks of code, can any of this be down by providing shims for for older Swift version that expose the new api names instead? Like:

https://github.com/apple/swift-protobuf/blob/master/Sources/SwiftProtobuf/MathUtils.swift#L42-L88

This way the code gets update to the Swift 5 syntax and the conditionals are kept to a minium reducing the chance of a bug that can exist in two almost identical blocks.

Yep! I'll take a stab at it.

@tbkka
Copy link
Contributor

tbkka commented Mar 19, 2019

The project at first glance supports Swift versions right back to 3.0,...

Our policy is to support one full major version back. Since 5.0 is the current release version, that means we still support 4.0 and later. Once 5.2 is released, we'll be able to use #if compiler since we'll only support >= 4.2 at that point.

@thomasvl
Copy link
Collaborator

thomasvl commented Mar 20, 2019

I also don't know the #compiler would be correct, as some of these seem to be API changes. API changes should be tied to the language version, not the underlying compiler version/release. If they were tied to the compiler doesn't that mean some code that compiles with Swift 4.x would suddenly stop compiling in 4.x mode with the 5 compiler?

ps - I completely read that initial comment as #if swift(), adding to the confusion here. 😄

@alanzeino
Copy link
Contributor Author

I also don't know the #compiler would be correct, as some of these seem to be API changes. API changes should be tied to the language version, not the underlying compiler version/release. If they were tied to the compiler doesn't that mean some code that compiles with Swift 4.x would suddenly stop compiling in 4.x mode with the 5 compiler?

ps - I completely read that initial comment as #if swift(), adding to the confusion here. 😄

Yep you're totally right. These changes are all Swift 5 compiler in 5.0 mode, not backwards compatible with 5 compiler in 4.2 mode.

Copy link
Contributor

@tbkka tbkka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few suggestions to simplify/improve the code.

Note: You don't need to fix all the Swift 5 issues at once. Multiple smaller PRs are easier to review.

Sources/SwiftProtobuf/AnyMessageStorage.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/Data+Extensions.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/JSONEncoder.swift Outdated Show resolved Hide resolved
@tbkka
Copy link
Contributor

tbkka commented Mar 21, 2019

@alanzeino -- I'm testing a branch where I've globally replaced all of the Data(bytes:) with Data(_:). Once I finish running tests (for Swift 4.0 through 5.0), I'll put up a PR for that. So I think you can focus on just the 24 warnings about withUnsafeBytes and withUnsafeMutableBytes. If you can fix those, I think I've got the rest of the 5.0 issues covered.

@tbkka tbkka changed the title Update for Swift 5 compiler Swift 5: adopt new Data.withUnsafeBytes API Mar 21, 2019
@alanzeino
Copy link
Contributor Author

@alanzeino -- I'm testing a branch where I've globally replaced all of the Data(bytes:) with Data(_:). Once I finish running tests (for Swift 4.0 through 5.0), I'll put up a PR for that. So I think you can focus on just the 24 warnings about withUnsafeBytes and withUnsafeMutableBytes. If you can fix those, I think I've got the rest of the 5.0 issues covered.

Yep, I'll rebase.

@keith
Copy link

keith commented Mar 27, 2019

@thomasvl @tbkka think we could merge this so there can be a new release that supports Swift 5?

@tbkka
Copy link
Contributor

tbkka commented Mar 28, 2019

The following seems sufficient to emulate the Swift 5 API on Swift 4:

#if !swift(>=5.0)
extension Data {
  func withUnsafeBytes<T>(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T {
    let c = count
    return try withUnsafeBytes { (p: UnsafePointer<UInt8>) throws -> T in
      try body(UnsafeRawBufferPointer(start: p, count: c))
    }
  }
  mutating func withUnsafeMutableBytes<T>(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
    let c = count
    return try withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) throws -> T in
      try body(UnsafeMutableRawBufferPointer(start: p, count: c))
    }
  }
}
#endif

Note that every place we used the old withUnsafeBytes APIs, we always used it with UInt8, so this is actually correct for us.

Using the above, I've experimentally switched a couple of places to use the Swift 5 APIs -- the result seems to work correctly on both Swift 4 and Swift 5. In the process, I did see some spots where the resulting code could probably be simplified, but I think such improvements can wait until after we get a clean build.

@alanzeino
Copy link
Contributor Author

The following seems sufficient to emulate the Swift 5 API on Swift 4:

#if !swift(>=5.0)
extension Data {
  func withUnsafeBytes<T>(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T {
    let c = count
    return try withUnsafeBytes { (p: UnsafePointer<UInt8>) throws -> T in
      try body(UnsafeRawBufferPointer(start: p, count: c))
    }
  }
  mutating func withUnsafeMutableBytes<T>(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
    let c = count
    return try withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) throws -> T in
      try body(UnsafeMutableRawBufferPointer(start: p, count: c))
    }
  }
}
#endif

Note that every place we used the old withUnsafeBytes APIs, we always used it with UInt8, so this is actually correct for us.

Using the above, I've experimentally switched a couple of places to use the Swift 5 APIs -- the result seems to work correctly on both Swift 4 and Swift 5. In the process, I did see some spots where the resulting code could probably be simplified, but I think such improvements can wait until after we get a clean build.

Ah I didn't think to hardcode the type. Updating now.

@alanzeino
Copy link
Contributor Author

alanzeino commented Mar 28, 2019

Updated all the cases. One thing to mention, looks like the master branch does not compile right now. So I did this rebased on top of a commit that did compile, then once I verified that it all compiled after my changes, I rebased so that the diff didn't have any conflicts.

Any plan to add a CI check to these PRs?

@tbkka
Copy link
Contributor

tbkka commented Mar 28, 2019

We don't have PR testing, but we do have CI for the master branch:

https://ci.swift.org/job/swift-protobuf-linux-ubuntu-16_04/

According to that, the master branch did build last night. I'll take a look when I get a chance.

@alanzeino
Copy link
Contributor Author

alanzeino commented Mar 28, 2019

We don't have PR testing, but we do have CI for the master branch:

https://ci.swift.org/job/swift-protobuf-linux-ubuntu-16_04/

According to that, the master branch did build last night. I'll take a look when I get a chance.

If I were to guess, I'd say that master branch build uses swift build and not xcodebuild build — and the reason why this doesn't compile is because a new type (TextFormatEncodingOptions) was added in a new file, but the .xcodeproj wasn't updated to include that new file.

That also raises another question I had — should we add the .xcodeproj to the .gitignore and just generate it using Swift PM? Then we can add a CI job that does both swift build and then swift package generate-xcodeproj && xcodebuild build -scheme SwiftProtobuf_iOS , which would catch these project inconsistencies.

Copy link
Contributor

@tbkka tbkka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better! The one cleanup I'd like to see right away is to fix a few places where the closure is accessing the Data object directly instead of using the buffer argument. I've marked some of them.

Sources/SwiftProtobuf/AnyMessageStorage.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/BinaryDecoder.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/Google_Protobuf_Any+Extensions.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/JSONEncoder.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/JSONScanner.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/Message+JSONArrayAdditions.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/Message+TextFormatAdditions.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/TextFormatEncoder.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift Outdated Show resolved Hide resolved
Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift Outdated Show resolved Hide resolved
@tbkka
Copy link
Contributor

tbkka commented Mar 30, 2019

Does your branch pass the test suite? (E.g. make test?)

I'm seeing failures here in the following three tests:
SwiftProtobufTests.Test_JSON testMultipleFields
SwiftProtobufTests.Test_JSON_Array testTwoObjectsWithMultipleFields
SwiftProtobufTests.Test_Map_JSON testMapInt32Bytes

I find it interesting that the failing cases all relate to JSON and all of them have a bytes field which is empty.

@thomasvl
Copy link
Collaborator

thomasvl commented Apr 1, 2019

That also raises another question I had — should we add the .xcodeproj to the .gitignore and just generate it using Swift PM? Then we can add a CI job that does both swift build and then swift package generate-xcodeproj && xcodebuild build -scheme SwiftProtobuf_iOS , which would catch these project inconsistencies.

Since SwiftPM doesn't really do iOS, and to better support users wanting Carthage, etc.; I think we end up needing a project file checked in. 🙁 The Makefile does have a bunch of Xcode related targets (test-xcode*), but I don't think most people bother to check them often.

@alanzeino
Copy link
Contributor Author

Does your branch pass the test suite? (E.g. make test?)

I'm seeing failures here in the following three tests:
SwiftProtobufTests.Test_JSON testMultipleFields
SwiftProtobufTests.Test_JSON_Array testTwoObjectsWithMultipleFields
SwiftProtobufTests.Test_Map_JSON testMapInt32Bytes

I find it interesting that the failing cases all relate to JSON and all of them have a bytes field which is empty.

I should've taken your advice and just converted a few cases just to be safe. Unfortunately I have a bit of a problem in that I'm starting at a new employer next week that might not want me working on Open Source so how about for now I just land the back–ported extension in Data+Extensions.swift and convert one other place for reference, and I'll leave the rest to you since it would be a more careful strategy to do them piecemeal?

@tbkka
Copy link
Contributor

tbkka commented Apr 4, 2019

If you can fix the style issues I pointed out earlier, I'll see if I can debug that test failure.

@alanzeino
Copy link
Contributor Author

If you can fix the style issues I pointed out earlier, I'll see if I can debug that test failure.

Okay!

Copy link
Contributor

@tbkka tbkka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured out why zero-length bytes fields were failing.

Sources/SwiftProtobuf/JSONScanner.swift Show resolved Hide resolved
@tbkka
Copy link
Contributor

tbkka commented Apr 4, 2019

Looks good. I'm going to run some more tests before I merge this.

@alanzeino
Copy link
Contributor Author

Cool! Lemme know if there's more changes I should make.

@tbkka
Copy link
Contributor

tbkka commented Apr 5, 2019

I ran tests on Ubuntu with Swift 4.0 through a 5.0 beta and it all looks good. Thanks for working through this!

@tbkka tbkka merged commit 3dd275f into apple:master Apr 5, 2019
@alanzeino alanzeino deleted the swift5 branch April 6, 2019 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants