简体   繁体   中英

Associated type constraints with generic types and protocols enables bypass of the WHERE clause

To fully understand the issues I have commented my thoughts in the code. For easy viewing you can copy a paste into a SWIFT playground. Unsure if this is a bug or a fundamental misunderstanding of type inference. I was able to bypass a WHERE clause by "tricking" the type inference to let me send a different type.

    import Foundation

protocol Container {
  associatedtype Item
  mutating func append(_ item: Item)
  var count: Int { get }
  subscript(i: Int) -> Item { get }
}

struct Stack<U>: Container {
  var items = [U]()
  mutating func push(_ item: U) {
    items.append(item)
  }
  mutating func pop() -> U {
    return items.removeLast()
  }
  // conformance to the Container protocol
  mutating func append(_ item: U) {
    self.push(item)
  }
  var count: Int {
    return items.count
  }
  subscript(i: Int) -> U {
    return items[i]
  }
}

struct Devin {} /// This is to describe a custom type.

extension Devin: Container {
  //typealias Item = String

  /// NOTE: Eventhough the typealias is omitted, the type is inferred as String due to the pattern defined here.
  mutating func append(_ item: String) {
    /// do nothing
  }
  var count: Int {
    /// do nothing
    return 0
  }
  subscript(i: Int) -> String {
    /// do nothing
    return "0"
  }
}

/// and here comes the confusing part.......

// Using a PROTOCOL in its ASSOCIATED TYPE'S constraints

protocol SuffixableContainer: Container {
    /// Here we are saying SUFFIX like the ITEM constraint before is a type that conforms to this protocol being defined and the actual constraint is the where clause.
  associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    /// Suffix has two constraints: It must conform to the SuffixableContainer protocol (the protocol currently being defined)
    /// and its Item type must be the same as the container’s Item type. ( what does it look like when they arent the same? )
    /// to understand whats happening here you have to see how its being used.
    ///
                               /// here is where its getting used and this is the important piece
    func suffix(_ size: Int) -> Suffix
}

extension Stack: SuffixableContainer {
                                /// when conforming
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}

/// in the commented out code below:
/// Eventhough, returning a stack in the generic form passed, returning one of a different type than the STRING that was inferred will throw an error.
/// Here we can return an instance of Devin and be fine but when we return an instance of a Stack that is a INT type it errors.
//extension Devin: SuffixableContainer {
//  func suffix(_ size: Int) -> Stack<Int> {
//    return Stack<Int>()
//  }
//}
/// Error:  note: requirement specified as 'Self.Item' == 'Self.Suffix.Item' [with Self = Devin]
/// This is the behavior you would expect

/// But here is a different story
/// Return (  Stack = just fine | Devin = just fine | Stack<Devin> = no go my friend )
extension Devin: SuffixableContainer {
  func suffix(_ size: Int) -> Devin {
    print(size)
    return Devin()
  }
}

print(Devin.Suffix.self)
print(Devin.Item.self)
if Devin.Item.self == Devin.Suffix.self {
  print("It Worked")
} else {
  print("They arent the same")
  /// For those who can guess it will print that they aren't the same which violates the where clause
}
let store = Devin.suffix(Devin())
/// Not sure I understand why this works


/// now here is where I can really see the issue.
/// After printing the two types Devin does not equal String &
/// Devin and String are not the same types and the where clause didnt throw any error.
/// And for whatever reason I can pass the Devin object

The where clause does not restrict Devin.Suffix to be the same as Devin.Item it restricts Devin.Suffix.Item to be the same as Devin.Item which it is.

Leaving everything else the same, the following code prints It Worked .

print(Devin.Suffix.Item.self)
print(Devin.Item.self)
if Devin.Item.self == Devin.Suffix.Item.self {
  print("It Worked")
} else {
  print("They aren't the same")
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM