@@ -209,7 +209,7 @@ def rank_dirs(g, terms, depth=3, topk=DIR_EVIDENCE):
209209 return ranked
210210
211211
212- def pick_seeds (g , scored , terms , want , named = ()):
212+ def pick_seeds (g , scored , terms , want , named = (), prefer = frozenset () ):
213213 """The entry points, in four passes, each only filling what the one before left:
214214
215215 1. what the question NAMES: a declaration it spells out (`IRouter.RouteAsync`), a route it quotes
@@ -221,17 +221,28 @@ def pick_seeds(g, scored, terms, want, named=()):
221221 3. the best symbol for each term still uncovered, as before, so a task naming unrelated concepts still enters the
222222 graph at each of them;
223223 4. the best overall.
224+
225+ Equal scores are common: `findByCode` on a mapper, a repository, a service and a query class all match the same
226+ two words the same way. A tie used to fall to the graph's symbol ids, which are hashes that change with the
227+ directory a project is indexed in, so the same question on the same code picked a different four of nine from one
228+ copy to the next. A tie now goes first to `prefer` (a declaration bound by a text file of the kind the question
229+ asks about: the mapper method whose XML holds the SQL, when the question asks which SQL runs), then to the one
230+ whose own name holds more of the matched words, then to its name and place, which do not move.
224231 """
225232 seeds , seen , covered = [], set (), set ()
226233 def take (sid , why , sc ):
227234 if sid in seen or len (seeds ) >= want : return False
228235 seen .add (sid ); seeds .append ((sid , why , sc )); covered .update (scored .get (sid , (0 , []))[1 ]); return True
229236 for sid , why in named :
230237 take (sid , why , scored .get (sid , (0 , []))[0 ])
231- ranked = sorted (scored .items (), key = lambda kv : - kv [1 ][0 ])
238+ # the task's words in the declaration's OWN name beat the same words reached through its owner or its path:
239+ # `OrderCreatedEvent` says more about "where is an order created" than `Order.getCreatedAt` does
240+ own = lambda sid : sum (t in ((g .sym .get (sid ) or {}).get ('name' ) or '' ).lower () for t in scored .get (sid , (0 , []))[1 ])
241+ tie = lambda sid : (sid not in prefer , - own (sid ), g .disp (sid ) or '' , g .loc (sid ) or '' , sid )
242+ ranked = sorted (scored .items (), key = lambda kv : (- kv [1 ][0 ],) + tie (kv [0 ]))
232243 floor = 0.05 * ranked [0 ][1 ][0 ] if ranked else 0
233244 multi = [(sid , sc , m ) for sid , (sc , m ) in ranked if len (m ) >= 2 and sc >= floor ]
234- multi .sort (key = lambda r : (- len (r [2 ]), - r [1 ]))
245+ multi .sort (key = lambda r : (- len (r [2 ]), - r [1 ]) + tie ( r [ 0 ]) )
235246 for i , (sid , sc , m ) in enumerate (multi ):
236247 if len (seeds ) >= want : break
237248 if not set (m ) - covered : continue
@@ -241,7 +252,7 @@ def pick_seeds(g, scored, terms, want, named=()):
241252 if sib >= FLOW_FAN : break
242253 if m2 == m and sc2 >= 0.5 * sc and take (sid2 , "matches '" + "' + '" .join (m2 ) + "'" , sc2 ): sib += 1
243254 best = {}
244- for sid , (sc , matched ) in scored . items () :
255+ for sid , (sc , matched ) in ranked :
245256 for t in matched :
246257 cur = best .get (t )
247258 if cur is None or sc > cur [0 ]: best [t ] = (sc , sid )
@@ -585,6 +596,19 @@ def text_bindings(g, sids, under=(), want_ext=()):
585596 return [r for _k , r in sorted (out )]
586597
587598
599+ def asked_bound (g , scored , text , under = (), cap = 300 ):
600+ """{symbol id} among the multi-term matches that a text file of the kind the question asks about binds by name.
601+ Empty unless the question asks about such a file (SQL, configuration, a template): only then is the tree walked,
602+ and only then does it break a tie between equally scored entry points (pick_seeds)."""
603+ kinds = asked_exts (text )
604+ if not kinds or not NONSOURCE_ASK .search (text or '' ): return frozenset ()
605+ cands = sorted ((sid for sid , (_sc , m ) in scored .items () if len (m ) >= 2 ), key = lambda s : - scored [s ][0 ])[:cap ]
606+ by_disp = collections .defaultdict (set )
607+ for sid in cands : by_disp [g .disp (sid )].add (sid )
608+ return frozenset (sid for f , _l , d , _n in text_bindings (g , cands , under = under , want_ext = kinds )
609+ if f .endswith (kinds ) for sid in by_disp .get (d , ()))
610+
611+
588612_SRC = {}
589613def source_lines (repo , f ):
590614 """The file's lines, read once. The graph stores repo-relative paths; a run may be started anywhere."""
@@ -990,7 +1014,7 @@ def main(argv):
9901014 # themselves, and on Python every class has one, so they can be a third of this list while
9911015 # naming nowhere to go.
9921016 scored = {sid : v for sid , v in scored .items () if not is_synthetic (g .sym .get (sid , {}).get ('name' ))}
993- seeds = pick_seeds (g , scored , terms , seeds_wanted , named )
1017+ seeds = pick_seeds (g , scored , terms , seeds_wanted , named , prefer = asked_bound ( g , scored , body , text_dirs ) )
9941018
9951019 RESULT ['terms' ] = list (terms )
9961020 RESULT ['scope' ], RESULT ['scope_offered' ] = (scopes [0 ] if scopes else None ), bool (scope_offered )
@@ -1123,10 +1147,10 @@ def main(argv):
11231147 sp = span_of .get ((f , nm ))
11241148 if not sp : continue
11251149 a , b = sp
1126- body = source_lines (g .repo , f )[a - 1 :min (b , a + SOURCE_LINES )]
1127- if not body : continue
1150+ code = source_lines (g .repo , f )[a - 1 :min (b , a + SOURCE_LINES )] # not `body`: that is the question
1151+ if not code : continue
11281152 print (f" { '' :22} { nm } ({ f } :{ a } )" )
1129- for k , ln in enumerate (body , a ):
1153+ for k , ln in enumerate (code , a ):
11301154 print (f" { '' :22} { k :5} | { ln .rstrip ()[:110 ]} " )
11311155 if b > a + SOURCE_LINES : print (f" { '' :22} { '' :5} | … +{ b - a - SOURCE_LINES } more line(s)" )
11321156 if foot and budget > 0 : print (foot + ", tests and benchmarks not listed" )
@@ -1144,9 +1168,13 @@ def main(argv):
11441168 if g .has ('overrides' ) and seed_ids :
11451169 ph = ',' .join ('?' * len (seed_ids ))
11461170 impls = {r [0 ] for r in g .q (f"SELECT overriding_method_id FROM overrides WHERE method_id IN ({ ph } )" , * seed_ids )} - seed_ids
1171+ # nearer through the implementation than through the seeds' own closure counts as nearer: the mapper call
1172+ # in the impl is one hop from it however far the closure walk happened to place it (hop 3 is outside the ring)
11471173 for x , d in neighbourhood (g , [i for i in impls if i in g .sym ], cap = 1 ).items ():
1148- if x not in seed_ids and x in g .sym and x not in depth : depth [x ] = d + 1 ; ring .append (x )
1149- near = list (seed_ids ) + sorted (ring , key = lambda x : (not bodiless (g , x ), depth [x ], x ))[:300 ]
1174+ if x in seed_ids or x not in g .sym or depth .get (x , d + 2 ) <= d + 1 : continue
1175+ if depth .get (x , 3 ) > 2 : ring .append (x )
1176+ depth [x ] = d + 1
1177+ near = list (seed_ids ) + sorted (ring , key = lambda x : (not bodiless (g , x ), depth [x ], g .disp (x ) or '' , x ))[:300 ]
11501178 kinds = asked_exts (body )
11511179 binds = text_bindings (g , near , under = text_dirs , want_ext = kinds )
11521180 # the question named a kind of file (SQL: mapper XML or .sql): when some binding is of that kind, the others
0 commit comments