Go structural: implement deep-expression matching

ober

03a2b049a82b73a6cb29627c8e8bbd345e7cee50

diff --git a/lib/semgrep/match/structural.sls b/lib/semgrep/match/structural.sls
index fe75bc4..c1954be 100644
--- a/lib/semgrep/match/structural.sls
+++ b/lib/semgrep/match/structural.sls
@@ -976,6 +976,63 @@
                (if (null? ys)
                    (loop (cdr remaining) inner-acc)
                    (inner (cdr ys) (cons (car ys) inner-acc)))))))
+  (def (go-deep-pattern-arg pattern)
+       (and (string=? (node-type pattern) "call_expression")
+            (>= (node-matchable-child-count pattern) 2)
+            (let ([fn (node-matchable-child pattern 0)])
+              (and fn
+                   (let ([is-deep (and (string=?
+                                         (node-type fn)
+                                         "identifier")
+                                       (string=?
+                                         (node-text fn)
+                                         deep-token-name))])
+                     (node-close! fn)
+                     (and is-deep
+                          (let ([args (node-matchable-child pattern 1)])
+                            (and args
+                                 (let ([arg (single-named-child args)])
+                                   (node-close! args)
+                                   arg)))))))))
+  (def (go-deep-match-here-or-descendant
+         language
+         pat
+         target
+         bindings)
+       (let ([here (structural-node-matches
+                     language
+                     pat
+                     target
+                     bindings)])
+         (if (not (null? here))
+             here
+             (let loop ([i 0])
+               (if (>= i (node-matchable-child-count target))
+                   '()
+                   (let ([child (node-matchable-child target i)])
+                     (if child
+                         (let ([r (go-deep-match-here-or-descendant
+                                    language
+                                    pat
+                                    child
+                                    bindings)])
+                           (node-close! child)
+                           (if (null? r) (loop (+ i 1)) r))
+                         (loop (+ i 1)))))))))
+  (def (go-deep-expression-matches
+         language
+         pattern
+         target
+         bindings)
+       (let ([arg (go-deep-pattern-arg pattern)])
+         (and arg
+              (let ([result (go-deep-match-here-or-descendant
+                              language
+                              arg
+                              target
+                              bindings)])
+                (node-close! arg)
+                result))))
   (def (structural-node-match
          language
          pattern
@@ -995,6 +1052,12 @@
        (let ([text (node-text pattern)])
          (cond
            [(ellipsis-node? pattern) (list bindings)]
+           [(go-deep-expression-matches
+              language
+              pattern
+              target
+              bindings) =>
+            (lambda (matches) matches)]
            [(quoted-ellipsis-metavariable-name-from-text text) =>
             (lambda (name)
               (let ([next (if (string=? language "json")
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index 850782a..4f26a00 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -4,10 +4,10 @@
   ("src/semgrep/lang.ss" . "6982E07679D20836")
   ("src/semgrep/parse/parse-target.ss" . "B1616180DE7038ED")
   ("src/semgrep/scan.ss" . "A09A5F4FE16A2036")
-  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
   ("src/semgrep/output/text.ss" . "BE476CB84B807FBA")
-  ("src/semgrep/rule.ss" . "E12C108153C181FA")
+  ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1")
   ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0")
-  ("src/semgrep/match/structural.ss" . "F46F2E199123036E")
+  ("src/semgrep/rule.ss" . "E12C108153C181FA")
+  ("src/semgrep/match/structural.ss" . "F93B68FF81D41586")
   ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E")
   ("src/semgrep/cli.ss" . "EBDC4B1DAD3F13CC"))
diff --git a/src/semgrep/match/structural.ss b/src/semgrep/match/structural.ss
index ca2351f..0decc04 100644
--- a/src/semgrep/match/structural.ss
+++ b/src/semgrep/match/structural.ss
@@ -1008,6 +1008,50 @@
               (loop (cdr remaining) inner-acc)
               (inner (cdr ys) (cons (car ys) inner-acc)))))))
 
+;; Go deep-expression operator `<... X ...>`, rewritten to __sg_deep__(X).
+;; If `pattern` is such a call, return the inner pattern node X (caller closes),
+;; else #f.
+(def (go-deep-pattern-arg pattern)
+  (and (string=? (node-type pattern) "call_expression")
+       (>= (node-matchable-child-count pattern) 2)
+       (let ([fn (node-matchable-child pattern 0)])
+         (and fn
+              (let ([is-deep (and (string=? (node-type fn) "identifier")
+                                  (string=? (node-text fn) deep-token-name))])
+                (node-close! fn)
+                (and is-deep
+                     (let ([args (node-matchable-child pattern 1)])
+                       (and args
+                            (let ([arg (single-named-child args)])
+                              (node-close! args)
+                              arg)))))))))
+
+;; X matches `target` or any of its descendants (deep search).
+(def (go-deep-match-here-or-descendant language pat target bindings)
+  (let ([here (structural-node-matches language pat target bindings)])
+    (if (not (null? here))
+        here
+        (let loop ([i 0])
+          (if (>= i (node-matchable-child-count target))
+              '()
+              (let ([child (node-matchable-child target i)])
+                (if child
+                    (let ([r (go-deep-match-here-or-descendant
+                               language pat child bindings)])
+                      (node-close! child)
+                      (if (null? r) (loop (+ i 1)) r))
+                    (loop (+ i 1)))))))))
+
+;; Returns the match list (possibly empty) if `pattern` is a deep-expression,
+;; or #f if it is not (so the caller falls through to normal matching).
+(def (go-deep-expression-matches language pattern target bindings)
+  (let ([arg (go-deep-pattern-arg pattern)])
+    (and arg
+         (let ([result (go-deep-match-here-or-descendant
+                         language arg target bindings)])
+           (node-close! arg)
+           result))))
+
 (def (structural-node-match language pattern target bindings)
   (let ([matches (structural-node-matches language pattern target bindings)])
     (and (not (null? matches))
@@ -1017,6 +1061,8 @@
   (let ([text (node-text pattern)])
     (cond
       [(ellipsis-node? pattern) (list bindings)]
+      [(go-deep-expression-matches language pattern target bindings)
+       => (lambda (matches) matches)]
       [(quoted-ellipsis-metavariable-name-from-text text)
        => (lambda (name)
             (let ([next (if (string=? language "json")