Is there a violation here? Or is (my) Playgrounds broken again?

This is in a new Xcode 11 playground:

public struct DivisibilityIterator<Element: BinaryInteger> where Element.Magnitude == Element {
    enum State {
        case zero
        case one
        case other(divisor: Element, dividend: Element)
    }
    var state: State
    init<T: BinaryInteger>(divisor: T, firstDividend: T) where T.Magnitude == Element {
        let absoluteDivisor = divisor.magnitude
        switch absoluteDivisor {
        case 0:
            state = .zero  // 1
        case 1:
            state = .one  // 1
        default:
            let reducedDividend = firstDividend % divisor
            let absoluteDividend: Element
            if reducedDividend < 0 {
                absoluteDividend = absoluteDivisor - reducedDividend.magnitude  // 2
            } else {
                absoluteDividend = reducedDividend.magnitude  // 2
            }
            state = .other(absoluteDivisor, absoluteDividend)  // 1
        }
    }
}

The errors at 1 are: "'DivisibilityIterator' requires the types 'Element' and 'Element.Magnitude' be equivalent". The ones at 2 are: "Cannot assign value of type 'T.Magnitude' to type 'Element'", with a fix-it of: "Insert ' as! Element'".

Error 1 makes no sense because it's completely irrelevant to what I'm doing there! Error 2 makes no sense because the initializer's where clause already requires the types to be the same. Is there some subtle rules of Swift I'm missing in either case?

I tried moving the State type to a separate enum generic type; got the same error. I closed the playground window, quit Xcode, and reopened both; didn't help. I haven't rebooted the computer (yet). That has helped in the past, but it's totally unacceptable that the Playgrounds system is that fragile to need that workaround, especially that there's no guarantee that it'll work.

A more compact code for error 1

struct Foo<Element: BinaryInteger> where Element.Magnitude == Element {
    var value: Int

    init<T: BinaryInteger>(divisor: T) where T.Magnitude == Element {
        value = 1 /* 'Foo<Element>' requires the types 'Element' and 'Element.Magnitude' be equivalent */
    }
}

Looks like T.Magnitude == Element makes compiler forget the Element constraint, or something. It looks like a bug.

This version crashes the compiler:

public struct Test<E: BinaryInteger> where E.Magnitude == E {
    var x: Int = 0
    init<F: BinaryInteger>(_: F) where F.Magnitude == E { }
}

Can you file a bug on bugs.swift.org? Thanks!

I opened SR-11531 for the inscrutable error message and SR-11532 for the compiler crash.

4 Likes

I did a workaround that's probably better anyway. Not that finding bugs isn't important, especially the weird ones.

struct ModuloIterator<Element: BinaryInteger> {
    let dividendLimit: Element.Magnitude
    var reducedDividend: Element.Magnitude
    init(divisor: Element, start: Element) {
        let remainder = start % divisor  // Crashes with out-of-range divisions.
        if remainder >= 0 {
            reducedDividend = remainder.magnitude
        } else if divisor >= 0 {
            reducedDividend = (remainder + divisor).magnitude
        } else {
            reducedDividend = (remainder - divisor).magnitude
        }
        dividendLimit = divisor.magnitude - 1
    }
}
extension ModuloIterator: IteratorProtocol {
    mutating func next() -> Element? {
        defer {
            if reducedDividend == dividendLimit {
                reducedDividend = 0
            } else {
                reducedDividend += 1
            }
        }
        return Element(reducedDividend)
    }
}

struct DivisibilityIterator<Integer: BinaryInteger> {
    enum State {
        case zero, one, other(ModuloIterator<Integer>)
    }
    var state: State
    init(divisor: Integer, start: Integer) {
        switch divisor.magnitude {
        case 0: state = .zero
        case 1: state = .one
        default: state = .other(ModuloIterator(divisor: divisor, start: start))
        }
    }
}
extension DivisibilityIterator: IteratorProtocol {
    mutating func next() -> Bool? {
        switch state {
        case .zero: return false
        case .one: return true
        case .other(var iterator):
            defer { state = .other(iterator) }
            return iterator.next().map { $0 == 0 }
        }
    }
}