@@ -144,6 +144,13 @@ pub struct FromJsonTarget {
144144/// Maps parseJson call-site spans to their resolved target info.
145145pub type FromJsonTargets = HashMap<Span, FromJsonTarget>;
146146
147+ /// Spans of full `sum f rel` applications (including the `rel |> sum f` pipe
148+ /// form) whose result type is a Float. Codegen passes this as an `is_float`
149+ /// flag to the runtime, which needs it ONLY for an EMPTY relation: with no
150+ /// summands there is nothing to take the numeric type from, so `sum` over an
151+ /// empty `[Float]` would otherwise return `Int 0` instead of `Float 0.0`.
152+ pub type SumFloatSpans = HashSet<Span>;
153+
147154/// Spans of `elem needle haystack` haystack arguments whose element type is a
148155/// SQL-pushable scalar (peeling aliases & refined types). Codegen consults these
149156/// sets to decide whether to push an `elem` down to SQL.
@@ -595,6 +602,10 @@ struct Infer {
595602 /// `monad_info` entry keyed by the call span so codegen can tell the
596603 /// runtime which applicative's `pure []` an EMPTY input must produce.
597604 traverse_calls: Vec<(Span, TyVar, TyVar)>,
605+ /// Full `sum f rel` applications: (call span, result type var).
606+ /// Post-inference, calls whose result is a Float land in `SumFloatSpans`
607+ /// so codegen can tell the runtime which zero an EMPTY relation sums to.
608+ sum_calls: Vec<(Span, TyVar)>,
598609
599610 /// Tracks `parseJson` application sites for compile-time FromJSON dispatch.
600611 /// Each entry records (app_span, return_type_var).
@@ -783,6 +794,7 @@ impl Infer {
783794 result_markers: Vec::new(),
784795 unify_depth: 0,
785796 traverse_calls: Vec::new(),
797+ sum_calls: Vec::new(),
786798 from_json_calls: Vec::new(),
787799 trait_call_vars: Vec::new(),
788800 show_calls: Vec::new(),
@@ -4974,6 +4986,16 @@ impl Infer {
49744986 self.traverse_calls.push((expr.span, *res_v, cont_v));
49754987 }
49764988
4989+ // Track full `sum f rel` applications: the resolved result type
4990+ // says whether this is a Float sum, which codegen hands to the
4991+ // runtime to pick the zero for an EMPTY relation (no summand
4992+ // there to infer the numeric type from).
4993+ if let ast::ExprKind::App { func: inner_f, .. } = &func.node
4994+ && matches!(&inner_f.node, ast::ExprKind::Var(n) if n == "sum")
4995+ && let Ty::Var(res_v) = &result_ty {
4996+ self.sum_calls.push((expr.span, *res_v));
4997+ }
4998+
49774999 // Track `elem needle haystack` haystack types for SQL pushdown.
49785000 // Curried: outer App's func is `App(Var("elem"), needle)`,
49795001 // outer App's arg is the haystack. Record only when the
@@ -5973,6 +5995,13 @@ impl Infer {
59735995 Box::new(result_ty.clone()),
59745996 );
59755997 self.unify(&rhs_ty, &fun_ty, span);
5998+ // `rel |> sum f` reaches codegen as an application carrying
5999+ // this pipe's span, so record it like the `sum f rel` form.
6000+ if let ast::ExprKind::App { func: inner_f, .. } = &rhs.node
6001+ && matches!(&inner_f.node, ast::ExprKind::Var(n) if n == "sum")
6002+ && let Ty::Var(res_v) = &result_ty {
6003+ self.sum_calls.push((span, *res_v));
6004+ }
59766005 result_ty
59776006 }
59786007 }
@@ -10378,6 +10407,24 @@ fn value_references_source_inner(
1037810407
1037910408// ── Public API ────────────────────────────────────────────────────
1038010409
10410+ /// What `check` hands to the later passes: diagnostics, the inferred types
10411+ /// themselves, and the span-keyed facts codegen cannot re-derive on its own
10412+ /// (monad kinds, refine/parseJson targets, `elem` pushdown eligibility, `show`
10413+ /// units, `sum`'s numeric result type).
10414+ pub type CheckOutput = (
10415+ Vec<Diagnostic>,
10416+ MonadInfo,
10417+ TypeInfo,
10418+ LocalTypeInfo,
10419+ RefineTargets,
10420+ RefinedTypeInfoMap,
10421+ FromJsonTargets,
10422+ ElemPushdownOk,
10423+ TraitCallTargets,
10424+ ShowUnitStrings,
10425+ SumFloatSpans,
10426+ );
10427+
1038110428/// Run type inference on a parsed module. Returns diagnostics,
1038210429/// resolved monad info for desugared do-blocks, and inferred type info
1038310430/// mapping declaration names to their display type strings.
@@ -10390,11 +10437,11 @@ fn value_references_source_inner(
1039010437///
1039110438/// Runs on a grown stack: a desugared `do` block nests one `__bind` per
1039210439/// statement, and `infer_expr` recurses through every level.
10393- pub fn check(module: &mut ast::Module) -> (Vec<Diagnostic>, MonadInfo, TypeInfo, LocalTypeInfo, RefineTargets, RefinedTypeInfoMap, FromJsonTargets, ElemPushdownOk, TraitCallTargets, ShowUnitStrings) {
10440+ pub fn check(module: &mut ast::Module) -> CheckOutput {
1039410441 crate::stack::grow(|| check_inner(module))
1039510442}
1039610443
10397- fn check_inner(module: &mut ast::Module) -> (Vec<Diagnostic>, MonadInfo, TypeInfo, LocalTypeInfo, RefineTargets, RefinedTypeInfoMap, FromJsonTargets, ElemPushdownOk, TraitCallTargets, ShowUnitStrings) {
10444+ fn check_inner(module: &mut ast::Module) -> CheckOutput {
1039810445 let mut infer = Infer::new();
1039910446
1040010447 // Phase 1: Collect type aliases, data types, constructors
@@ -10659,6 +10706,21 @@ fn check_inner(module: &mut ast::Module) -> (Vec<Diagnostic>, MonadInfo, TypeInf
1065910706 monad_info.entry(*span).or_insert(kind);
1066010707 }
1066110708
10709+ // Phase 5c: Resolve the numeric type of each full `sum f rel` call, keyed
10710+ // by the call span. Codegen passes it to the runtime, which uses it ONLY
10711+ // for the EMPTY-input result: no summands means no value to take the type
10712+ // from, and the zero must still be the one the program was checked against
10713+ // (`Float 0.0`, not `Int 0`).
10714+ let mut sum_float_spans = SumFloatSpans::new();
10715+ for (span, res_v) in &infer.sum_calls {
10716+ if matches!(
10717+ infer.apply(&Ty::Var(*res_v)).peel_alias(),
10718+ Ty::Float | Ty::FloatUnit(_)
10719+ ) {
10720+ sum_float_spans.insert(*span);
10721+ }
10722+ }
10723+
1066210724 // Export refined type predicates for codegen
1066310725 let refined_type_info: RefinedTypeInfoMap = infer
1066410726 .refined_types
@@ -10722,7 +10784,7 @@ fn check_inner(module: &mut ast::Module) -> (Vec<Diagnostic>, MonadInfo, TypeInf
1072210784 let local_type_info = infer.extract_local_type_info();
1072310785 let elem_pushdown_ok = infer.elem_pushdown_ok.clone();
1072410786
10725- (infer.to_diagnostics(), monad_info, type_info, local_type_info, refine_targets, refined_type_info, from_json_targets, elem_pushdown_ok, trait_call_targets, show_unit_strings)
10787+ (infer.to_diagnostics(), monad_info, type_info, local_type_info, refine_targets, refined_type_info, from_json_targets, elem_pushdown_ok, trait_call_targets, show_unit_strings, sum_float_spans )
1072610788}
1072710789
1072810790
@@ -11158,14 +11220,14 @@ mod tests {
1115811220 fn check_src(src: &str) -> Vec<Diagnostic> {
1115911221 let mut module = parse(src);
1116011222 crate::desugar::desugar(&mut module);
11161- let (diags, _monad_info, _type_info, _local_types, _refine_targets, _refined_types, _from_json, _elem_pushdown, _trait_calls, _show_units) = check(&mut module);
11223+ let (diags, _monad_info, _type_info, _local_types, _refine_targets, _refined_types, _from_json, _elem_pushdown, _trait_calls, _show_units, _sum_floats ) = check(&mut module);
1116211224 diags
1116311225 }
1116411226
1116511227 fn type_info_for(src: &str) -> TypeInfo {
1116611228 let mut module = parse(src);
1116711229 crate::desugar::desugar(&mut module);
11168- let (_diags, _monad_info, type_info, _local_types, _refine_targets, _refined_types, _from_json, _elem_pushdown, _trait_calls, _show_units) = check(&mut module);
11230+ let (_diags, _monad_info, type_info, _local_types, _refine_targets, _refined_types, _from_json, _elem_pushdown, _trait_calls, _show_units, _sum_floats ) = check(&mut module);
1116911231 type_info
1117011232 }
1117111233
0 commit comments