Skip to content

Commit 0504a34

Browse files
feat: Add utility function for binary search based on symbol names (#273)
Also add clearer documentation related to guarantees around document canonicalization.
1 parent 853034d commit 0504a34

File tree

5 files changed

+69
-3
lines changed

5 files changed

+69
-3
lines changed

bindings/go/scip/canonicalize.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package scip
22

3-
// CanonicalizeDocument deterministically re-orders the fields of the given document.
3+
// CanonicalizeDocument deterministically sorts and merges fields of the given document.
4+
//
5+
// Post-conditions:
6+
// 1. The Occurrences field only contains those with well-formed ranges
7+
// (length 3 or 4, potentially empty).
8+
// 2. The Occurrences field is sorted in ascending order of ranges based on
9+
// Range.CompareStrict
10+
// 3. The Symbols field is sorted in ascending order based on the symbol name,
11+
// and SymbolInformation values for the same name will have been merged.
412
func CanonicalizeDocument(document *Document) *Document {
513
document.Occurrences = CanonicalizeOccurrences(document.Occurrences)
614
document.Symbols = CanonicalizeSymbols(document.Symbols)

bindings/go/scip/sort.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package scip
22

33
import (
44
"sort"
5+
6+
"golang.org/x/exp/slices"
57
)
68

79
// FindSymbol returns the symbol with the given name in the given document. If there is no symbol by
8-
// that name, this function returns nil.
10+
// that name, this function returns nil. Prefer using FindSymbolBinarySearch over this function.
911
func FindSymbol(document *Document, symbolName string) *SymbolInformation {
1012
for _, symbol := range document.Symbols {
1113
if symbol.Symbol == symbolName {
@@ -16,6 +18,25 @@ func FindSymbol(document *Document, symbolName string) *SymbolInformation {
1618
return nil
1719
}
1820

21+
// FindSymbolBinarySearch attempts to find the SymbolInformation in the given document.
22+
//
23+
// Pre-condition: The symbols array must be sorted in ascending order based on the symbol name,
24+
// and SymbolInformation values must be merged. This guarantee is upheld by CanonicalizeDocument.
25+
func FindSymbolBinarySearch(canonicalizedDocument *Document, symbolName string) *SymbolInformation {
26+
i, found := slices.BinarySearchFunc(canonicalizedDocument.Symbols, symbolName, func(sym *SymbolInformation, lookup string) int {
27+
if sym.Symbol < lookup {
28+
return -1
29+
} else if sym.Symbol == lookup {
30+
return 0
31+
}
32+
return 1
33+
})
34+
if found {
35+
return canonicalizedDocument.Symbols[i]
36+
}
37+
return nil
38+
}
39+
1940
// SortDocuments sorts the given documents slice (in-place) and returns it (for convenience). Documents
2041
// are sorted in ascending order of their relative path.
2142
func SortDocuments(documents []*Document) []*Document {

bindings/go/scip/sort_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"testing"
55

66
"github.com/google/go-cmp/cmp"
7+
"github.com/stretchr/testify/require"
8+
"golang.org/x/exp/slices"
9+
"pgregory.net/rapid"
710
)
811

912
func TestFindOccurrences(t *testing.T) {
@@ -122,3 +125,34 @@ func TestSortRanges(t *testing.T) {
122125
t.Errorf("unexpected occurrence order (-want +got):\n%s", diff)
123126
}
124127
}
128+
129+
func genSymbolInfo() *rapid.Generator[*SymbolInformation] {
130+
return rapid.Custom(func(t *rapid.T) *SymbolInformation {
131+
return &SymbolInformation{Symbol: rapid.String().Draw(t, "symbol")}
132+
})
133+
}
134+
135+
func TestFindSymbolBinarySearch(t *testing.T) {
136+
rapid.Check(t, func(t *rapid.T) {
137+
symbolInfoGen := genSymbolInfo()
138+
symbolInfos := rapid.SliceOfN(symbolInfoGen, 0, 10).Draw(t, "symbolInfos")
139+
doc := &Document{Symbols: symbolInfos}
140+
canonicalDoc := CanonicalizeDocument(doc)
141+
for _, symbolInfo := range symbolInfos {
142+
got := FindSymbolBinarySearch(canonicalDoc, symbolInfo.Symbol)
143+
require.NotNil(t, got)
144+
require.Equal(t, symbolInfo.Symbol, got.Symbol)
145+
}
146+
other := rapid.String().Draw(t, "otherSymbol")
147+
isInOriginalSlice := slices.ContainsFunc(symbolInfos, func(info *SymbolInformation) bool {
148+
return info.Symbol == other
149+
})
150+
got := FindSymbolBinarySearch(canonicalDoc, other)
151+
if isInOriginalSlice {
152+
require.NotNil(t, got)
153+
require.Equal(t, other, got.Symbol)
154+
} else {
155+
require.Nil(t, got)
156+
}
157+
})
158+
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/sourcegraph/sourcegraph/lib v0.0.0-20220511160847-5a43d3ea24eb
1717
github.com/stretchr/testify v1.8.4
1818
github.com/urfave/cli/v2 v2.25.7
19+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
1920
golang.org/x/tools v0.12.0
2021
google.golang.org/protobuf v1.31.0
2122
pgregory.net/rapid v1.1.0

go.sum

+3-1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
454454
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
455455
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
456456
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
457+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
458+
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
457459
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
458460
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
459461
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -661,4 +663,4 @@ mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
661663
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
662664
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
663665
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
664-
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
666+
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

0 commit comments

Comments
 (0)