Units and measurement: trouble implementing Dimension subclass in Swift 5

I have a UnitCurrency dimension and Money measurement type in RolePlayingCore, here on GitHub:




It is failing to build or test in the latest Xcode 10.2 beta with Swift 5, and I’m getting lost in the documentation and error messages.


To get you up to speed where I am at, I’ll create a simplified use case, and walk you from Swift 4.2,  to my Swift 5 conundrum.


Let’s say we are using Xcode 10.1 and Swift 4.2. Say we have two currencies, gp and cp, where one has a coefficient of 1 and the other 0.01:


public class UnitCurrency : Dimension {
    static let gp = UnitCurrency(symbol: "gp", converter: UnitConverterLinear(coefficient: 1.0))
    static let cp = UnitCurrency(symbol: "cp", converter: UnitConverterLinear(coefficient: 0.01))



Let’s implement a unit test point for confirming which one is the base unit, like this:


        XCTAssertEqual(UnitCurrency.baseUnit(), UnitCurrency.gp, "base unit should be gp")


It will fail, as expected:


XCTAssertEqual failed: throwing "*** You must override baseUnit in your class TestDimension.UnitCurrency to define its base unit." - base unit should be goldPieces


The class is currently missing an implementation for the class func baseUnit(). The documentation for Foundation Dimension on https://developer.apple.com/documentation/foundation/dimension describes a class func baseUnit() requirement, but suggests declaring a public static let in the section on “Creating a Custom NSDimension Subclass”, like this:


    public static let baseUnit = gp


That doesn’t work, as the test will still fail, so it appears that one must implement the class func after all, like this:


    public override class func baseUnit() -> UnitCurrency {
        return gp


With that, the unit test point will pass.


Let’s further define a Money measurement for this unit:


public typealias Money = Measurement


And, a unit test point with some assertions about how to combine money:


        let goldPieces = Money(value: 25, unit: .gp)
        let copperPieces = Money(value: 14, unit: .cp)

        let totalPieces = goldPieces - copperPieces
        XCTAssertEqual(totalPieces.value, 24.86, accuracy: 0.0001, "adding coins in gp")
        let totalPiecesInCopper = totalPieces.converted(to: .cp)
        XCTAssertEqual(totalPiecesInCopper.value, 2486, accuracy: 0.01, "adding coins in cp")


The build and test succeeds. Next, we close the project, open it in Xcode 10.2 beta, and change the Swift version to 5 (note: using Convert > To Current Swift Syntax does not produce any code changes), and now it complains about the declaration for class func baseUnit():


Cannot override a Self return type with a non-Self return type


If I change the return type to “Self”, then instead I get this error message on the next line returning gp:


Cannot convert return expression of type 'UnitCurrency' to return type 'Self'


It has a fix-it suggestion to add “as! Self”, which then results in a handful of warnings and errors, including a fix-it suggestion to remove “as! Self”:


Error: 'Self' is only available in a protocol or as the result of a method in a class; did you mean 'UnitCurrency'? 
Fix-it: Replace Self with UnitCurrency (which gets me back to the original error)
Error: Cannot convert return expression of type 'UnitCurrency' to return type 'Self' 
Fix-it: Insert as! Self (so 'gp as! Self as! Self')
Warning: Forced cast of 'UnitCurrency' to same type has no effect 
Fix-It: Replace 'as! Self' with '' (which gets me back to the previous error).


Trying to use the static let also gets me back to the original problem in Swift 4.2.


I’m kind of stuck now, seeking advice on what to try next.

Powered by WPeMatico

You may also like...

Comments are closed.