@@ -272,6 +272,22 @@ function initSearch(rawSearchIndex) {
272
272
* Special type name IDs for searching by both tuple and unit (`()` syntax).
273
273
*/
274
274
let typeNameIdOfTupleOrUnit ;
275
+ /**
276
+ * Special type name IDs for searching `fn`.
277
+ */
278
+ let typeNameIdOfFn ;
279
+ /**
280
+ * Special type name IDs for searching `fnmut`.
281
+ */
282
+ let typeNameIdOfFnMut ;
283
+ /**
284
+ * Special type name IDs for searching `fnonce`.
285
+ */
286
+ let typeNameIdOfFnOnce ;
287
+ /**
288
+ * Special type name IDs for searching higher order functions (`->` syntax).
289
+ */
290
+ let typeNameIdOfHof ;
275
291
276
292
/**
277
293
* Add an item to the type Name->ID map, or, if one already exists, use it.
@@ -464,6 +480,21 @@ function initSearch(rawSearchIndex) {
464
480
}
465
481
}
466
482
483
+ function makePrimitiveElement ( name , extra ) {
484
+ return Object . assign ( {
485
+ name,
486
+ id : null ,
487
+ fullPath : [ name ] ,
488
+ pathWithoutLast : [ ] ,
489
+ pathLast : name ,
490
+ normalizedPathLast : name ,
491
+ generics : [ ] ,
492
+ bindings : new Map ( ) ,
493
+ typeFilter : "primitive" ,
494
+ bindingName : null ,
495
+ } , extra ) ;
496
+ }
497
+
467
498
/**
468
499
* @param {ParsedQuery } query
469
500
* @param {ParserState } parserState
@@ -501,18 +532,7 @@ function initSearch(rawSearchIndex) {
501
532
}
502
533
const bindingName = parserState . isInBinding ;
503
534
parserState . isInBinding = null ;
504
- return {
505
- name : "never" ,
506
- id : null ,
507
- fullPath : [ "never" ] ,
508
- pathWithoutLast : [ ] ,
509
- pathLast : "never" ,
510
- normalizedPathLast : "never" ,
511
- generics : [ ] ,
512
- bindings : new Map ( ) ,
513
- typeFilter : "primitive" ,
514
- bindingName,
515
- } ;
535
+ return makePrimitiveElement ( "never" , { bindingName } ) ;
516
536
}
517
537
const quadcolon = / : : \s * : : / . exec ( path ) ;
518
538
if ( path . startsWith ( "::" ) ) {
@@ -671,28 +691,19 @@ function initSearch(rawSearchIndex) {
671
691
let start = parserState . pos ;
672
692
let end ;
673
693
if ( "[(" . indexOf ( parserState . userQuery [ parserState . pos ] ) !== - 1 ) {
674
- let endChar = ")" ;
675
- let name = "()" ;
676
- let friendlyName = "tuple" ;
677
-
678
- if ( parserState . userQuery [ parserState . pos ] === "[" ) {
679
- endChar = "]" ;
680
- name = "[]" ;
681
- friendlyName = "slice" ;
682
- }
694
+ let endChar = ")" ;
695
+ let name = "()" ;
696
+ let friendlyName = "tuple" ;
697
+
698
+ if ( parserState . userQuery [ parserState . pos ] === "[" ) {
699
+ endChar = "]" ;
700
+ name = "[]" ;
701
+ friendlyName = "slice" ;
702
+ }
683
703
parserState . pos += 1 ;
684
704
const { foundSeparator } = getItemsBefore ( query , parserState , generics , endChar ) ;
685
705
const typeFilter = parserState . typeFilter ;
686
- const isInBinding = parserState . isInBinding ;
687
- if ( typeFilter !== null && typeFilter !== "primitive" ) {
688
- throw [
689
- "Invalid search type: primitive " ,
690
- name ,
691
- " and " ,
692
- typeFilter ,
693
- " both specified" ,
694
- ] ;
695
- }
706
+ const bindingName = parserState . isInBinding ;
696
707
parserState . typeFilter = null ;
697
708
parserState . isInBinding = null ;
698
709
for ( const gen of generics ) {
@@ -702,23 +713,26 @@ if (parserState.userQuery[parserState.pos] === "[") {
702
713
}
703
714
if ( name === "()" && ! foundSeparator && generics . length === 1 && typeFilter === null ) {
704
715
elems . push ( generics [ 0 ] ) ;
716
+ } else if ( name === "()" && generics . length === 1 && generics [ 0 ] . name === "->" ) {
717
+ // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`
718
+ // not `primitive:"()"<"->"<output=b, (a,)>>`
719
+ generics [ 0 ] . typeFilter = typeFilter ;
720
+ elems . push ( generics [ 0 ] ) ;
705
721
} else {
722
+ if ( typeFilter !== null && typeFilter !== "primitive" ) {
723
+ throw [
724
+ "Invalid search type: primitive " ,
725
+ name ,
726
+ " and " ,
727
+ typeFilter ,
728
+ " both specified" ,
729
+ ] ;
730
+ }
706
731
parserState . totalElems += 1 ;
707
732
if ( isInGenerics ) {
708
733
parserState . genericsElems += 1 ;
709
734
}
710
- elems . push ( {
711
- name : name ,
712
- id : null ,
713
- fullPath : [ name ] ,
714
- pathWithoutLast : [ ] ,
715
- pathLast : name ,
716
- normalizedPathLast : name ,
717
- generics,
718
- bindings : new Map ( ) ,
719
- typeFilter : "primitive" ,
720
- bindingName : isInBinding ,
721
- } ) ;
735
+ elems . push ( makePrimitiveElement ( name , { bindingName, generics } ) ) ;
722
736
}
723
737
} else {
724
738
const isStringElem = parserState . userQuery [ start ] === "\"" ;
@@ -805,6 +819,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
805
819
const oldIsInBinding = parserState . isInBinding ;
806
820
parserState . isInBinding = null ;
807
821
822
+ // ML-style Higher Order Function notation
823
+ //
824
+ // a way to search for any closure or fn pointer regardless of
825
+ // which closure trait is used
826
+ //
827
+ // Looks like this:
828
+ //
829
+ // `option<t>, (t -> u) -> option<u>`
830
+ // ^^^^^^
831
+ //
832
+ // The Rust-style closure notation is implemented in getNextElem
833
+ let hofParameters = null ;
834
+
808
835
let extra = "" ;
809
836
if ( endChar === ">" ) {
810
837
extra = "<" ;
@@ -825,6 +852,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
825
852
throw [ "Unexpected " , endChar , " after " , "=" ] ;
826
853
}
827
854
break ;
855
+ } else if ( endChar !== "" && isReturnArrow ( parserState ) ) {
856
+ // ML-style HOF notation only works when delimited in something,
857
+ // otherwise a function arrow starts the return type of the top
858
+ if ( parserState . isInBinding ) {
859
+ throw [ "Unexpected " , "->" , " after " , "=" ] ;
860
+ }
861
+ hofParameters = [ ...elems ] ;
862
+ elems . length = 0 ;
863
+ parserState . pos += 2 ;
864
+ foundStopChar = true ;
865
+ foundSeparator = false ;
866
+ continue ;
867
+ } else if ( c === " " ) {
868
+ parserState . pos += 1 ;
869
+ continue ;
828
870
} else if ( isSeparatorCharacter ( c ) ) {
829
871
parserState . pos += 1 ;
830
872
foundStopChar = true ;
@@ -904,6 +946,27 @@ if (parserState.userQuery[parserState.pos] === "[") {
904
946
// in any case.
905
947
parserState . pos += 1 ;
906
948
949
+ if ( hofParameters ) {
950
+ // Commas in a HOF don't cause wrapping parens to become a tuple.
951
+ // If you want a one-tuple with a HOF in it, write `((a -> b),)`.
952
+ foundSeparator = false ;
953
+ // HOFs can't have directly nested bindings.
954
+ if ( [ ...elems , ...hofParameters ] . some ( x => x . bindingName ) || parserState . isInBinding ) {
955
+ throw [ "Unexpected " , "=" , " within " , "->" ] ;
956
+ }
957
+ // HOFs are represented the same way closures are.
958
+ // The arguments are wrapped in a tuple, and the output
959
+ // is a binding, even though the compiler doesn't technically
960
+ // represent fn pointers that way.
961
+ const hofElem = makePrimitiveElement ( "->" , {
962
+ generics : hofParameters ,
963
+ bindings : new Map ( [ [ "output" , [ ...elems ] ] ] ) ,
964
+ typeFilter : null ,
965
+ } ) ;
966
+ elems . length = 0 ;
967
+ elems [ 0 ] = hofElem ;
968
+ }
969
+
907
970
parserState . typeFilter = oldTypeFilter ;
908
971
parserState . isInBinding = oldIsInBinding ;
909
972
@@ -1635,6 +1698,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
1635
1698
) {
1636
1699
// () matches primitive:tuple or primitive:unit
1637
1700
// if it matches, then we're fine, and this is an appropriate match candidate
1701
+ } else if ( queryElem . id === typeNameIdOfHof &&
1702
+ ( fnType . id === typeNameIdOfFn || fnType . id === typeNameIdOfFnMut ||
1703
+ fnType . id === typeNameIdOfFnOnce )
1704
+ ) {
1705
+ // -> matches fn, fnonce, and fnmut
1706
+ // if it matches, then we're fine, and this is an appropriate match candidate
1638
1707
} else if ( fnType . id !== queryElem . id || queryElem . id === null ) {
1639
1708
return false ;
1640
1709
}
@@ -1829,6 +1898,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
1829
1898
typePassesFilter ( elem . typeFilter , row . ty ) && elem . generics . length === 0 &&
1830
1899
// special case
1831
1900
elem . id !== typeNameIdOfArrayOrSlice && elem . id !== typeNameIdOfTupleOrUnit
1901
+ && elem . id !== typeNameIdOfHof
1832
1902
) {
1833
1903
return row . id === elem . id || checkIfInList (
1834
1904
row . generics ,
@@ -2991,7 +3061,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
2991
3061
*/
2992
3062
function buildFunctionTypeFingerprint ( type , output , fps ) {
2993
3063
let input = type . id ;
2994
- // All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
3064
+ // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
2995
3065
// Differentiating between arrays and slices, if the user asks for it, is
2996
3066
// still done in the matching algorithm.
2997
3067
if ( input === typeNameIdOfArray || input === typeNameIdOfSlice ) {
@@ -3000,6 +3070,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
3000
3070
if ( input === typeNameIdOfTuple || input === typeNameIdOfUnit ) {
3001
3071
input = typeNameIdOfTupleOrUnit ;
3002
3072
}
3073
+ if ( input === typeNameIdOfFn || input === typeNameIdOfFnMut ||
3074
+ input === typeNameIdOfFnOnce ) {
3075
+ input = typeNameIdOfHof ;
3076
+ }
3003
3077
// http://burtleburtle.net/bob/hash/integer.html
3004
3078
// ~~ is toInt32. It's used before adding, so
3005
3079
// the number stays in safe integer range.
@@ -3103,6 +3177,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
3103
3177
typeNameIdOfUnit = buildTypeMapIndex ( "unit" ) ;
3104
3178
typeNameIdOfArrayOrSlice = buildTypeMapIndex ( "[]" ) ;
3105
3179
typeNameIdOfTupleOrUnit = buildTypeMapIndex ( "()" ) ;
3180
+ typeNameIdOfFn = buildTypeMapIndex ( "fn" ) ;
3181
+ typeNameIdOfFnMut = buildTypeMapIndex ( "fnmut" ) ;
3182
+ typeNameIdOfFnOnce = buildTypeMapIndex ( "fnonce" ) ;
3183
+ typeNameIdOfHof = buildTypeMapIndex ( "->" ) ;
3106
3184
3107
3185
// Function type fingerprints are 128-bit bloom filters that are used to
3108
3186
// estimate the distance between function and query.
0 commit comments