Skip to content

Commit

Permalink
Suggested only the minimal type disambiguation
Browse files Browse the repository at this point in the history
rdar://136207880
  • Loading branch information
d-ronnqvist committed Nov 5, 2024
1 parent 784ba58 commit 6109d28
Show file tree
Hide file tree
Showing 3 changed files with 446 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -387,31 +387,37 @@ extension PathHierarchy.DisambiguationContainer {
) -> [(value: PathHierarchy.Node, disambiguation: Disambiguation)] {
var collisions: [(value: PathHierarchy.Node, disambiguation: Disambiguation)] = []

let groupedByTypeCount = [Int?: [Element]](grouping: elements, by: { types($0)?.count })
for (typesCount, elements) in groupedByTypeCount {
guard let typesCount else { continue }
guard elements.count > 1 else {
typealias ElementAndTypeNames = (element: Element, typeNames: [String])
var groupedByTypeCount: [Int: [ElementAndTypeNames]] = [:]
for element in elements {
guard let typeNames = types(element) else { continue }

groupedByTypeCount[typeNames.count, default: []].append((element, typeNames))
}

for (numberOfTypeNames, elementAndTypeNamePairs) in groupedByTypeCount {
guard elementAndTypeNamePairs.count > 1 else {
// Only one element has this number of types. Disambiguate with only underscores.
let element = elements.first!
let (element, _) = elementAndTypeNamePairs.first!
guard remainingIDs.contains(element.node.identifier) else { continue } // Don't disambiguate the same element more than once
collisions.append((value: element.node, disambiguation: makeDisambiguation(.init(repeating: "_", count: typesCount))))
collisions.append((value: element.node, disambiguation: makeDisambiguation(.init(repeating: "_", count: numberOfTypeNames))))
remainingIDs.remove(element.node.identifier)
continue
}
guard typesCount > 0 else { continue } // Need at least one return value to disambiguate

for typeIndex in 0..<typesCount {
let grouped = [String: [Element]](grouping: elements, by: { types($0)![typeIndex] })
for (returnType, elements) in grouped where elements.count == 1 {
// Only one element has this return type
let element = elements.first!
guard remainingIDs.contains(element.node.identifier) else { continue } // Don't disambiguate the same element more than once
var disambiguation = [String](repeating: "_", count: typesCount)
disambiguation[typeIndex] = returnType
collisions.append((value: element.node, disambiguation: makeDisambiguation(disambiguation)))
remainingIDs.remove(element.node.identifier)
continue
guard numberOfTypeNames > 0 else {
continue // Need at least one type name to disambiguate (when there are multiple elements without parameters or return values)
}

let disambiguation = minimalSuggestedDisambiguation(listOfOverloadTypeNames: elementAndTypeNamePairs.map(\.typeNames))

for (pair, disambiguation) in zip(elementAndTypeNamePairs, disambiguation) {
guard let disambiguation else {
continue // This element can't be uniquely disambiguated using these types
}
guard remainingIDs.contains(pair.element.node.identifier) else { continue } // Don't disambiguate the same element more than once
collisions.append((value: pair.element.node, disambiguation: makeDisambiguation(disambiguation)))
remainingIDs.remove(pair.element.node.identifier)
}
}
return collisions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation

extension PathHierarchy.DisambiguationContainer {

/// Returns the minimal suggested type-signature disambiguation for a list of overloads with lists of type names (either parameter types or return value types).
///
/// For example, the following type names
/// ```
/// String Int Double
/// String? Int Double
/// String? Int Float
/// ```
/// can be disambiguated using:
/// - `String,_,_` because only the first overload has `String` as its first type
/// - `String?,_,Double` because the combination of `String?` as its first type and `Double` as the last type is unique to the second overload.
/// - `_,_,Float` because only the last overload has `Float` as its last type.
///
/// - Parameter listOfTypeNames: The lists of type-name lists to shrink to the minimal unique combinations of type names
static func minimalSuggestedDisambiguation(listOfOverloadTypeNames: [[String]]) -> [[String]?] {
// The number of types in each list
guard let numberOfTypes = listOfOverloadTypeNames.first?.count, 0 < numberOfTypes else {
return []
}

guard listOfOverloadTypeNames.dropFirst().allSatisfy({ $0.count == numberOfTypes }) else {
assertionFailure("Overloads should always have the same number of parameters")
return []
}

// We find the minimal suggested type-signature disambiguation in two steps.
//
// First, we compute which type names occur in which _other_ overloads.
// For example, these type names (left) have these commonalities between each other (right).
//
// String Int Double [ ] [ 12] [ 1 ]
// String? Int Double [ 2] [0 2] [0 ]
// String? Int Float [ 1 ] [01 ] [ ]
//
// Note that each cell doesn't include its own index.

let table: [[Set<Int>]] = listOfOverloadTypeNames.map { typeNames in
typeNames.indexed().map { column, name in
Set(listOfOverloadTypeNames.indices.filter {
$0 != row && listOfOverloadTypeNames[$0][column] == name
})
}
}


// Second, we iterate over each overload's type names to find the shortest disambiguation.
return listOfOverloadTypeNames.indexed().map { row, overload in
var shortestDisambiguationSoFar: (indicesToInclude: Set<Int>, length: Int)?

// For each overload we iterate over the possible parameter combinations with increasing number of elements in each combination.
for typeNamesToInclude in uniqueSortedCombinations(ofNumbersUpTo: numberOfTypes) {
// Stop if we've already found a match with fewer parameters than this
guard typeNamesToInclude.count <= (shortestDisambiguationSoFar?.indicesToInclude.count ?? .max) else {
break
}

let firstTypeNameToInclude = typeNamesToInclude.first! // The generated `typeNamesToInclude` is never empty.
// Compute which other overloads this combinations of type names also could refer to.
let overlap = typeNamesToInclude.dropFirst().reduce(into: table[row][firstTypeNameToInclude]) { partialResult, index in
partialResult.formIntersection(table[row][index])
}

guard overlap.isEmpty else {
// This combination of parameters doesn't disambiguate the result
continue
}

// Track the combined length of these type names in case another overload with the same number of type names is shorter.
let length = typeNamesToInclude.reduce(0) { partialResult, index in
partialResult + overload[index].count
}
if length < (shortestDisambiguationSoFar?.length ?? .max) {
shortestDisambiguationSoFar = (Set(typeNamesToInclude), length)
}
}

guard let (indicesToInclude, _) = shortestDisambiguationSoFar else {
// This overload can't be uniquely disambiguated by these type names
return nil
}

// Found the fewest (and shortest) type names that uniquely disambiguate this overload.
// To compute the overload, start with all the type names and replace the unused ones with "_"
var disambiguation = overload
for col in overload.indices where !indicesToInclude.contains(col) {
disambiguation[col] = "_"
}
return disambiguation
}
}
}

/// Returns a sequence of unique combinations up a given upper bounds, with increasing number of elements in each combination.
///
/// For example, when `upperBounds` is `4`, the sequence will first contain all the 1-element combinations:
/// ```
/// [1], [2], [3], [4],
/// ```
/// Next, it will contains all the 2-element combinations (in _some_ inner order):
/// ```
/// [1, 2], [1, 3], [2, 3], [1, 4], [2, 4], [3, 4],
/// ```
/// Next, it will contains all the 3-element combinations (in _some_ inner order):
/// ```
/// [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4],
/// ```
/// Finally, it will contains all the only 4-element combinations:
/// ```
/// [1, 2, 3, 4],
/// ```
private func uniqueSortedCombinations(ofNumbersUpTo upperBounds: Int) -> some Sequence<[Int]> {

/// An iterator that generates sequences of unique number combinations, as described above.
///
/// The iterator works by generating all combinations of a given size and then returning each element from the list.
/// Once the iterator reaches the end of one size, it generates the next size and returns each element from that list.
struct SortedCombinationsIterator: IteratorProtocol {
typealias Element = [Int]

private var upperBounds: Int
private var currentSize = 0
private var current: [Element]
private var currentSlice: ArraySlice<Element>

init(upperBounds: Int) {
self.upperBounds = upperBounds
self.current = (0..<upperBounds).map { [$0] }
self.currentSlice = current[...]
}

mutating func next() -> Element? {
if !currentSlice.isEmpty {
return currentSlice.removeFirst()
}
guard currentSize < upperBounds else {
return nil
}
currentSize += 1
current = combinations(upTo: upperBounds, previous: current)
guard !current.isEmpty else {
return nil
}
currentSlice = current[...]
return currentSlice.removeFirst()
}

func combinations(upTo upperBounds: Int, previous: [Element]) -> [Element] {
precondition(upperBounds > 0)

var result: [Element] = []
result.reserveCapacity(upperBounds * upperBounds-1)
for number in 0 ..< upperBounds {
for var existing in previous where !existing.contains(number) && (existing.last ?? .max) < number {
existing.append(number)

result.append(existing)
}
}
return result
}
}

return IteratorSequence(SortedCombinationsIterator(upperBounds: upperBounds))
}
Loading

0 comments on commit 6109d28

Please sign in to comment.