@@ -272,6 +272,22 @@ function initSearch(rawSearchIndex) {
272272 * Special type name IDs for searching by both tuple and unit (`()` syntax).
273273 */
274274 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 ;
275291
276292 /**
277293 * Add an item to the type Name->ID map, or, if one already exists, use it.
@@ -464,6 +480,21 @@ function initSearch(rawSearchIndex) {
464480 }
465481 }
466482
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+
467498 /**
468499 * @param {ParsedQuery } query
469500 * @param {ParserState } parserState
@@ -501,18 +532,7 @@ function initSearch(rawSearchIndex) {
501532 }
502533 const bindingName = parserState . isInBinding ;
503534 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 } ) ;
516536 }
517537 const quadcolon = / : : \s * : : / . exec ( path ) ;
518538 if ( path . startsWith ( "::" ) ) {
@@ -671,28 +691,19 @@ function initSearch(rawSearchIndex) {
671691 let start = parserState . pos ;
672692 let end ;
673693 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+ }
683703 parserState . pos += 1 ;
684704 const { foundSeparator } = getItemsBefore ( query , parserState , generics , endChar ) ;
685705 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 ;
696707 parserState . typeFilter = null ;
697708 parserState . isInBinding = null ;
698709 for ( const gen of generics ) {
@@ -702,23 +713,26 @@ if (parserState.userQuery[parserState.pos] === "[") {
702713 }
703714 if ( name === "()" && ! foundSeparator && generics . length === 1 && typeFilter === null ) {
704715 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 ] ) ;
705721 } 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+ }
706731 parserState . totalElems += 1 ;
707732 if ( isInGenerics ) {
708733 parserState . genericsElems += 1 ;
709734 }
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 } ) ) ;
722736 }
723737 } else {
724738 const isStringElem = parserState . userQuery [ start ] === "\"" ;
@@ -805,6 +819,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
805819 const oldIsInBinding = parserState . isInBinding ;
806820 parserState . isInBinding = null ;
807821
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+
808835 let extra = "" ;
809836 if ( endChar === ">" ) {
810837 extra = "<" ;
@@ -825,6 +852,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
825852 throw [ "Unexpected " , endChar , " after " , "=" ] ;
826853 }
827854 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 ;
828870 } else if ( isSeparatorCharacter ( c ) ) {
829871 parserState . pos += 1 ;
830872 foundStopChar = true ;
@@ -904,6 +946,27 @@ if (parserState.userQuery[parserState.pos] === "[") {
904946 // in any case.
905947 parserState . pos += 1 ;
906948
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+
907970 parserState . typeFilter = oldTypeFilter ;
908971 parserState . isInBinding = oldIsInBinding ;
909972
@@ -1635,6 +1698,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
16351698 ) {
16361699 // () matches primitive:tuple or primitive:unit
16371700 // 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
16381707 } else if ( fnType . id !== queryElem . id || queryElem . id === null ) {
16391708 return false ;
16401709 }
@@ -1829,6 +1898,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
18291898 typePassesFilter ( elem . typeFilter , row . ty ) && elem . generics . length === 0 &&
18301899 // special case
18311900 elem . id !== typeNameIdOfArrayOrSlice && elem . id !== typeNameIdOfTupleOrUnit
1901+ && elem . id !== typeNameIdOfHof
18321902 ) {
18331903 return row . id === elem . id || checkIfInList (
18341904 row . generics ,
@@ -2991,7 +3061,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
29913061 */
29923062 function buildFunctionTypeFingerprint ( type , output , fps ) {
29933063 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.
29953065 // Differentiating between arrays and slices, if the user asks for it, is
29963066 // still done in the matching algorithm.
29973067 if ( input === typeNameIdOfArray || input === typeNameIdOfSlice ) {
@@ -3000,6 +3070,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
30003070 if ( input === typeNameIdOfTuple || input === typeNameIdOfUnit ) {
30013071 input = typeNameIdOfTupleOrUnit ;
30023072 }
3073+ if ( input === typeNameIdOfFn || input === typeNameIdOfFnMut ||
3074+ input === typeNameIdOfFnOnce ) {
3075+ input = typeNameIdOfHof ;
3076+ }
30033077 // http://burtleburtle.net/bob/hash/integer.html
30043078 // ~~ is toInt32. It's used before adding, so
30053079 // the number stays in safe integer range.
@@ -3103,6 +3177,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
31033177 typeNameIdOfUnit = buildTypeMapIndex ( "unit" ) ;
31043178 typeNameIdOfArrayOrSlice = buildTypeMapIndex ( "[]" ) ;
31053179 typeNameIdOfTupleOrUnit = buildTypeMapIndex ( "()" ) ;
3180+ typeNameIdOfFn = buildTypeMapIndex ( "fn" ) ;
3181+ typeNameIdOfFnMut = buildTypeMapIndex ( "fnmut" ) ;
3182+ typeNameIdOfFnOnce = buildTypeMapIndex ( "fnonce" ) ;
3183+ typeNameIdOfHof = buildTypeMapIndex ( "->" ) ;
31063184
31073185 // Function type fingerprints are 128-bit bloom filters that are used to
31083186 // estimate the distance between function and query.
0 commit comments