@@ -6,8 +6,8 @@ use itertools::{Itertools, izip};
66use ndc_core:: { StaticType , TypeSignature } ;
77use ndc_lexer:: Span ;
88use ndc_parser:: {
9- Binding , Expression , ExpressionLocation , ForBody , ForIteration , FunctionParameter , Lvalue ,
10- NodeId ,
9+ Binding , Candidate , Expression , ExpressionLocation , ForBody , ForIteration , FunctionParameter ,
10+ Lvalue , NodeId ,
1111} ;
1212
1313/// Side table holding semantic information keyed by AST node identity.
@@ -130,7 +130,7 @@ impl Analyser {
130130 return Ok ( StaticType :: Any ) ;
131131 } ;
132132
133- * resolved = Binding :: Resolved ( binding) ;
133+ * resolved = Binding :: Resolved ( Candidate :: scalar ( binding) ) ;
134134
135135 Ok ( self . scope_tree . get_type ( binding) . clone ( ) )
136136 }
@@ -202,12 +202,17 @@ impl Analyser {
202202 let right_type = self . analyse_or_any ( r_value) ;
203203 let arg_types = vec ! [ left_type, right_type] ;
204204
205- * resolved_assign_operation = self
206- . scope_tree
207- . resolve_function_binding ( & format ! ( "{operation}=" ) , & arg_types) ;
205+ // OpAssignment desugars to `x = x op y` where `op` is operator-form,
206+ // so vec dispatch must be available for both the in-place and the
207+ // fallback regular operator overload.
208+ * resolved_assign_operation = self . scope_tree . resolve_function_binding (
209+ & format ! ( "{operation}=" ) ,
210+ & arg_types,
211+ true ,
212+ ) ;
208213 * resolved_operation = self
209214 . scope_tree
210- . resolve_function_binding ( operation, & arg_types) ;
215+ . resolve_function_binding ( operation, & arg_types, true ) ;
211216
212217 if let Binding :: None = resolved_operation {
213218 self . emit ( AnalysisError :: function_not_found (
@@ -219,7 +224,7 @@ impl Analyser {
219224 let result_type = match resolved_operation {
220225 Binding :: Resolved ( res) => {
221226 if let StaticType :: Function { return_type, .. } =
222- self . scope_tree . get_type ( * res)
227+ self . scope_tree . get_type ( res. var )
223228 {
224229 Some ( return_type. as_ref ( ) . clone ( ) )
225230 } else {
@@ -256,15 +261,15 @@ impl Analyser {
256261 ..
257262 } = & value. expression
258263 {
259- let container_type = self . scope_tree . get_type ( * target) . clone ( ) ;
264+ let container_type = self . scope_tree . get_type ( target. var ) . clone ( ) ;
260265 if let Some ( elem_type) = container_type. index_element_type ( ) {
261266 let widened_elem = elem_type. lub ( & result_type) ;
262267 if widened_elem != elem_type {
263268 let new_container =
264269 container_type. with_element_type ( widened_elem) ;
265270 let _ = self
266271 . scope_tree
267- . update_binding_type ( * target, new_container) ;
272+ . update_binding_type ( target. var , new_container) ;
268273 }
269274 }
270275 }
@@ -405,14 +410,19 @@ impl Analyser {
405410 Expression :: Call {
406411 function,
407412 arguments,
413+ operator_form,
408414 } => {
409415 let mut type_sig = Vec :: with_capacity ( arguments. len ( ) ) ;
410416 for a in arguments {
411417 type_sig. push ( self . analyse_or_any ( a) ) ;
412418 }
413419
414- let callee_type =
415- self . resolve_function_with_argument_types ( function, & type_sig, * span) ;
420+ let callee_type = self . resolve_function_with_argument_types (
421+ function,
422+ & type_sig,
423+ * operator_form,
424+ * span,
425+ ) ;
416426
417427 let StaticType :: Function { return_type, .. } = callee_type else {
418428 if callee_type == StaticType :: Any {
@@ -483,6 +493,7 @@ impl Analyser {
483493 & mut self ,
484494 ident : & mut ExpressionLocation ,
485495 argument_types : & [ StaticType ] ,
496+ operator_form : bool ,
486497 span : Span ,
487498 ) -> StaticType {
488499 let ExpressionLocation {
@@ -497,7 +508,7 @@ impl Analyser {
497508
498509 let binding = self
499510 . scope_tree
500- . resolve_function_binding ( name, argument_types) ;
511+ . resolve_function_binding ( name, argument_types, operator_form ) ;
501512
502513 let out_type = match & binding {
503514 Binding :: None => {
@@ -508,20 +519,35 @@ impl Analyser {
508519 ) ) ;
509520 return StaticType :: Any ;
510521 }
511- Binding :: Resolved ( res) => self . scope_tree . get_type ( * res) . clone ( ) ,
512-
513- Binding :: Dynamic ( _) => {
514- // Dispatch is decided at runtime, so we have no sound static bound
515- // on the result. The runtime may pick a declared overload or fall
516- // through to elementwise (vectorized) dispatch, which can produce
517- // a value no declared overload returns — treating the LUB of
518- // declared returns as the result type is unsound and led to issue
519- // #139, where `let diff = a - b` over tuples was inferred as
520- // `Number` and a follow-up `diff * diff` then matched the numeric
521- // overload directly and bypassed dynamic dispatch entirely.
522+ Binding :: Resolved ( candidate) => {
523+ let return_type = candidate_return ( & self . scope_tree , candidate, argument_types) ;
524+ let scalar_type = self . scope_tree . get_type ( candidate. var ) . clone ( ) ;
525+ // Preserve `parameters` from the underlying scalar so any
526+ // downstream consumer that inspects the function shape sees
527+ // the original arity; only the return type changes for vec.
528+ match scalar_type {
529+ StaticType :: Function { parameters, .. } => StaticType :: Function {
530+ parameters,
531+ return_type : Box :: new ( return_type) ,
532+ } ,
533+ _ => StaticType :: Any ,
534+ }
535+ }
536+ Binding :: Dynamic ( candidates) => {
537+ // Runtime decides which candidate fires. LUB across every
538+ // candidate's inferred return type — scalars contribute their
539+ // declared return, vecs contribute `Tuple<elem_return; max_len>`.
540+ // `LUB(Tuple<...>, scalar) = Any` in the type lattice, so the
541+ // mixed case naturally falls to Any; pure-scalar Dynamic
542+ // recovers the precision PR #140 had to pessimise.
543+ let return_type = candidates
544+ . iter ( )
545+ . map ( |c| candidate_return ( & self . scope_tree , c, argument_types) )
546+ . reduce ( |a, b| a. lub ( & b) )
547+ . unwrap_or ( StaticType :: Any ) ;
522548 StaticType :: Function {
523549 parameters : None ,
524- return_type : Box :: new ( StaticType :: Any ) ,
550+ return_type : Box :: new ( return_type ) ,
525551 }
526552 }
527553 } ;
@@ -530,7 +556,52 @@ impl Analyser {
530556
531557 out_type
532558 }
559+ }
560+
561+ /// Returns the type a single candidate would produce for this call.
562+ ///
563+ /// Examples (assume `+(Int, Int) -> Int` is the underlying scalar):
564+ /// - scalar candidate, call args `[Int, Int]` → `Int`
565+ /// - vec candidate, call args `[Tuple<Int, Int>, Tuple<Int, Int>]` →
566+ /// `Tuple<Int, Int>` (two element calls, each returns `Int`)
567+ /// - vec candidate, call args `[Tuple<Int, Int>, Int]` →
568+ /// `Tuple<Int, Int>` (the scalar `Int` broadcasts)
569+ /// - vec candidate, call args `[Any, Any]` → `Any` (no tuple length
570+ /// visible at compile time, so we can't say how long the result is)
571+ fn candidate_return (
572+ scope_tree : & ScopeTree ,
573+ candidate : & Candidate ,
574+ argument_types : & [ StaticType ] ,
575+ ) -> StaticType {
576+ let StaticType :: Function { return_type, .. } = scope_tree. get_type ( candidate. var ) else {
577+ return StaticType :: Any ;
578+ } ;
579+ let scalar_return = return_type. as_ref ( ) . clone ( ) ;
580+ if !candidate. vectorized {
581+ return scalar_return;
582+ }
583+ match static_vec_axis_len ( argument_types) {
584+ Some ( len) => StaticType :: Tuple ( vec ! [ scalar_return; len] ) ,
585+ None => StaticType :: Any ,
586+ }
587+ }
533588
589+ /// Length of the first tuple-shaped argument, which is how many element
590+ /// calls a vec dispatch will make.
591+ ///
592+ /// Examples:
593+ /// - `[Tuple<Int, Int>, Int]` → `Some(2)`
594+ /// - `[Int, Tuple<Int, Int, Int>]` → `Some(3)`
595+ /// - `[Int, Int]` → `None` (vec doesn't apply)
596+ /// - `[Any, Any]` → `None` (we can't tell at compile time)
597+ fn static_vec_axis_len ( sig : & [ StaticType ] ) -> Option < usize > {
598+ sig. iter ( ) . find_map ( |t| match t {
599+ StaticType :: Tuple ( elems) if !elems. is_empty ( ) => Some ( elems. len ( ) ) ,
600+ _ => None ,
601+ } )
602+ }
603+
604+ impl Analyser {
534605 fn resolve_for_iterations (
535606 & mut self ,
536607 iterations : & mut [ ForIteration ] ,
@@ -660,8 +731,16 @@ impl Analyser {
660731 let get_args = [ type_of_index_target. clone ( ) , index_type. clone ( ) ] ;
661732 let set_args = [ type_of_index_target. clone ( ) , index_type, StaticType :: Any ] ;
662733
663- * resolved_get = Some ( self . scope_tree . resolve_function_binding ( "[]" , & get_args) ) ;
664- * resolved_set = Some ( self . scope_tree . resolve_function_binding ( "[]=" , & set_args) ) ;
734+ // Index syntax is not operator-form for vec purposes — there is no
735+ // natural element-wise broadcast story for `(list_a, list_b)[i]`.
736+ * resolved_get = Some (
737+ self . scope_tree
738+ . resolve_function_binding ( "[]" , & get_args, false ) ,
739+ ) ;
740+ * resolved_set = Some (
741+ self . scope_tree
742+ . resolve_function_binding ( "[]=" , & set_args, false ) ,
743+ ) ;
665744
666745 if let Some ( t) = type_of_index_target. index_element_type ( ) {
667746 Ok ( t)
0 commit comments