/// The overlap tests for a compositional-determinism decision set: a UPA violation /// is two DISTINCT particles in one decision set that can match the same element /// (same element name, or a wildcard admitting the other's name/namespace). Pure /// functions over ``CompItem`` sets, factored out of ``CompWalk`` so the walk file /// stays within its length budget; the walk accumulates decision sets and hands /// each to ``overlap(_:)``. enum CompConflict { static func overlap(_ set: [CompItem]) -> String? { var nameOwner: [PureXML.Model.QualifiedName: ObjectIdentifier] = [:] var wildcards: [(card: PureXML.Schema.Wildcard, particle: ObjectIdentifier)] = [] for item in set { switch item.label { case let .name(qname): if let owner = nameOwner[qname], owner != item.particle { return qname.localName } if nameOwner[qname] == nil { nameOwner[qname] = item.particle } case let .wildcard(card): wildcards.append((card, item.particle)) } } for (index, wildcard) in wildcards.enumerated() { // A wildcard conflicts with any name it admits, from a distinct particle, // and with any other wildcard it intersects, from a distinct particle. if nameOwner.contains(where: { wildcard.card.admits($0.key) && $0.value != wildcard.particle }) { return "any" } for other in wildcards[(index + 1)...] where other.particle != wildcard.particle && wildcardsOverlap(wildcard.card, other.card) { return "any" } } return nil } static func wildcardsOverlap(_ lhs: PureXML.Schema.Wildcard, _ rhs: PureXML.Schema.Wildcard) -> Bool { // Two wildcards overlap when some namespace name is admitted by both. Any // two `not` forms always share infinitely many named namespaces; a `not` // form overlaps a set when the set holds a named namespace it admits. switch (lhs.namespace, rhs.namespace) { case (.any, _), (_, .any): true case let (.enumerated(lhsSet), .enumerated(rhsSet)): !lhsSet.isDisjoint(with: rhsSet) case let (.enumerated(set), .notNamespace(name)), let (.notNamespace(name), .enumerated(set)): set.contains { !$0.isEmpty && $0 != name } case let (.enumerated(set), .notAbsent), let (.notAbsent, .enumerated(set)): set.contains { !$0.isEmpty } case (.notNamespace, .notNamespace), (.notNamespace, .notAbsent), (.notAbsent, .notNamespace), (.notAbsent, .notAbsent): true } } }