@@ -275,125 +275,96 @@ impl ScopeTree {
275275 }
276276 }
277277
278- pub ( crate ) fn resolve_function_dynamic (
279- & mut self ,
280- ident : & str ,
281- sig : & [ StaticType ] ,
282- ) -> Vec < ResolvedVar > {
278+ /// Resolve a function call binding in a single scope-chain walk.
279+ ///
280+ /// Combines what were previously three separate walks (`resolve_function`,
281+ /// `resolve_function_dynamic`, `get_all_bindings_by_name`) into one pass.
282+ /// At each scope the priorities are:
283+ /// 1. Exact type match or upvalue → return `Binding::Resolved` immediately
284+ /// 2. Compatible-type candidates → remember first set found (for `Binding::Dynamic`)
285+ /// 3. All same-named bindings → accumulate as last-resort fallback
286+ pub ( crate ) fn resolve_function_binding ( & mut self , ident : & str , sig : & [ StaticType ] ) -> Binding {
283287 let mut scope_ptr = self . current_scope_idx ;
284288 let mut env_scopes: Vec < usize > = Vec :: default ( ) ;
289+ let mut loose_candidates: Option < Vec < ResolvedVar > > = None ;
290+ let mut all_by_name: Vec < ResolvedVar > = Vec :: new ( ) ;
285291
286292 loop {
287- let candidates = self . scopes [ scope_ptr] . find_function_candidates ( ident, sig) ;
288- if !candidates. is_empty ( ) {
289- return candidates
290- . into_iter ( )
291- . map ( |slot| self . resolve_found_local ( ident, slot, & env_scopes) )
292- . collect ( ) ;
293- } else if let Some ( slot) = self . scopes [ scope_ptr] . find_upvalue ( ident) {
294- return vec ! [ self . resolve_found_upvalue( ident, slot, & env_scopes) ] ;
295- } else if let Some ( parent_idx) = self . scopes [ scope_ptr] . parent_idx {
296- if self . scopes [ scope_ptr] . creates_environment {
297- env_scopes. push ( scope_ptr) ;
298- }
299-
300- scope_ptr = parent_idx;
301- } else {
302- return self
303- . global_scope
304- . find_function_candidates ( ident, sig)
305- . into_iter ( )
306- . map ( |slot| ResolvedVar :: Global { slot } )
307- . collect ( ) ;
293+ // 1. Exact match → return immediately
294+ if let Some ( slot) = self . scopes [ scope_ptr] . find_function ( ident, sig) {
295+ return Binding :: Resolved ( self . resolve_found_local ( ident, slot, & env_scopes) ) ;
308296 }
309- }
310- }
311297
312- pub ( crate ) fn resolve_function_binding ( & mut self , ident : & str , sig : & [ StaticType ] ) -> Binding {
313- self . resolve_function ( ident, sig)
314- . map ( Binding :: Resolved )
315- . or_else ( || {
316- let loose_bindings = self . resolve_function_dynamic ( ident, sig) ;
317-
318- if loose_bindings. is_empty ( ) {
319- return None ;
320- }
298+ // 2. Upvalue with matching name → return immediately
299+ // (matches prior behavior where resolve_function returned upvalues
300+ // without type-checking; the correctness issue is tracked separately)
301+ if let Some ( slot) = self . scopes [ scope_ptr] . find_upvalue ( ident) {
302+ return Binding :: Resolved ( self . resolve_found_upvalue ( ident, slot, & env_scopes) ) ;
303+ }
321304
322- Some ( Binding :: Dynamic ( loose_bindings) )
323- } )
324- // If we can't find any function in scope that could match, fall back to all same-named
325- // bindings so runtime dynamic dispatch (including vectorization) can pick the right one.
326- . or_else ( || {
327- let all_bindings = self . get_all_bindings_by_name ( ident) ;
328- if all_bindings. is_empty ( ) {
329- return None ;
305+ // 3. Compatible candidates (keep only the first scope's matches — shadowing)
306+ if loose_candidates. is_none ( ) {
307+ let candidates = self . scopes [ scope_ptr] . find_function_candidates ( ident, sig) ;
308+ if !candidates. is_empty ( ) {
309+ loose_candidates = Some (
310+ candidates
311+ . into_iter ( )
312+ . map ( |slot| self . resolve_found_local ( ident, slot, & env_scopes) )
313+ . collect ( ) ,
314+ ) ;
330315 }
331- Some ( Binding :: Dynamic ( all_bindings) )
332- } )
333- . unwrap_or ( Binding :: None )
334- }
335-
336- pub ( crate ) fn get_all_bindings_by_name ( & mut self , ident : & str ) -> Vec < ResolvedVar > {
337- let mut results = Vec :: new ( ) ;
338- let mut scope_ptr = self . current_scope_idx ;
339- let mut env_scopes: Vec < usize > = Vec :: default ( ) ;
316+ }
340317
341- loop {
318+ // 4. All same-named bindings (accumulate across all scopes)
342319 let slots = self . scopes [ scope_ptr] . find_all_slots_by_name ( ident) ;
343- results . extend (
320+ all_by_name . extend (
344321 slots
345322 . into_iter ( )
346323 . map ( |slot| self . resolve_found_local ( ident, slot, & env_scopes) ) ,
347324 ) ;
348325
349- if let Some ( slot) = self . scopes [ scope_ptr] . find_upvalue ( ident) {
350- results. push ( self . resolve_found_upvalue ( ident, slot, & env_scopes) ) ;
351- }
352-
326+ // Advance to parent scope
353327 if let Some ( parent_idx) = self . scopes [ scope_ptr] . parent_idx {
354328 if self . scopes [ scope_ptr] . creates_environment {
355329 env_scopes. push ( scope_ptr) ;
356330 }
357-
358331 scope_ptr = parent_idx;
359332 } else {
360- let global_slots = self . global_scope . find_all_slots_by_name ( ident) ;
361- results. extend (
362- global_slots
333+ // Fall through to globals
334+ if let Some ( slot) = self . global_scope . find_function ( ident, sig) {
335+ return Binding :: Resolved ( ResolvedVar :: Global { slot } ) ;
336+ }
337+
338+ if loose_candidates. is_none ( ) {
339+ let candidates = self . global_scope . find_function_candidates ( ident, sig) ;
340+ if !candidates. is_empty ( ) {
341+ loose_candidates = Some (
342+ candidates
343+ . into_iter ( )
344+ . map ( |slot| ResolvedVar :: Global { slot } )
345+ . collect ( ) ,
346+ ) ;
347+ }
348+ }
349+
350+ all_by_name. extend (
351+ self . global_scope
352+ . find_all_slots_by_name ( ident)
363353 . into_iter ( )
364354 . map ( |slot| ResolvedVar :: Global { slot } ) ,
365355 ) ;
356+
366357 break ;
367358 }
368359 }
369360
370- results
371- }
372-
373- pub ( crate ) fn resolve_function (
374- & mut self ,
375- ident : & str ,
376- arg_types : & [ StaticType ] ,
377- ) -> Option < ResolvedVar > {
378- let mut scope_ptr = self . current_scope_idx ;
379- let mut env_scopes: Vec < usize > = Vec :: default ( ) ;
380-
381- loop {
382- if let Some ( slot) = self . scopes [ scope_ptr] . find_function ( ident, arg_types) {
383- return Some ( self . resolve_found_local ( ident, slot, & env_scopes) ) ;
384- } else if let Some ( slot) = self . scopes [ scope_ptr] . find_upvalue ( ident) {
385- return Some ( self . resolve_found_upvalue ( ident, slot, & env_scopes) ) ;
386- } else if let Some ( parent_idx) = self . scopes [ scope_ptr] . parent_idx {
387- if self . scopes [ scope_ptr] . creates_environment {
388- env_scopes. push ( scope_ptr) ;
389- }
390- scope_ptr = parent_idx;
391- } else {
392- return Some ( ResolvedVar :: Global {
393- slot : self . global_scope . find_function ( ident, arg_types) ?,
394- } ) ;
395- }
361+ if let Some ( candidates) = loose_candidates {
362+ return Binding :: Dynamic ( candidates) ;
363+ }
364+ if !all_by_name. is_empty ( ) {
365+ return Binding :: Dynamic ( all_by_name) ;
396366 }
367+ Binding :: None
397368 }
398369
399370 pub ( crate ) fn create_local_binding ( & mut self , ident : String , typ : StaticType ) -> ResolvedVar {
0 commit comments