Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions ndc_analyser/src/analyser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,20 +510,18 @@ impl Analyser {
}
Binding::Resolved(res) => self.scope_tree.get_type(*res).clone(),

Binding::Dynamic(candidates) => {
let return_type = candidates
.iter()
.map(|c| self.scope_tree.get_type(*c).clone())
.filter_map(|t| match t {
StaticType::Function { return_type, .. } => Some(*return_type),
_ => None,
})
.reduce(|a, b| a.lub(&b))
.unwrap_or(StaticType::Any);

Binding::Dynamic(_) => {
// Dispatch is decided at runtime, so we have no sound static bound
// on the result. The runtime may pick a declared overload or fall
// through to elementwise (vectorized) dispatch, which can produce
// a value no declared overload returns — treating the LUB of
// declared returns as the result type is unsound and led to issue
// #139, where `let diff = a - b` over tuples was inferred as
// `Number` and a follow-up `diff * diff` then matched the numeric
// overload directly and bypassed dynamic dispatch entirely.
StaticType::Function {
parameters: None,
return_type: Box::new(return_type),
return_type: Box::new(StaticType::Any),
Comment thread
timfennis marked this conversation as resolved.
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// BUG#0021 (issue #139): vectorized tuple arithmetic broke when the result
// of one tuple op was fed into another. The analyser inferred the result of
// `a - b` as `Number` (the LUB of the dynamic candidates' return types), so
// `diff * diff` resolved statically to a `(Number, Number) -> Number`
// overload via `Binding::Resolved` and bypassed OverloadSet dispatch — and
// therefore the VM's vectorized fallback. The native then bailed with
// "expected number, got Tuple<Int, Int, Int>".
let a = (1, 2, 3);
let b = (4, 5, 6);

assert_eq(a * b, (4, 10, 18));

let diff = a - b;
assert_eq(diff, (-3, -3, -3));
assert_eq(diff * diff, (9, 9, 9));

assert_eq((a - b) * (a - b), (9, 9, 9));

// Same hazard but the tuple elements are statically `Any`. `id` here erases
// type information so the tuples below are `Tuple<Any, Any, Any>`; this
// mirrors what happens with stdlib natives whose `Value` return type infers
// to `Any`. The vectorization detection must treat `Any` as a potentially-
// numeric element so chained ops still go through dynamic dispatch.
fn id(x) -> Any => x;
let av = (id(1), id(2), id(3));
let bv = (id(4), id(5), id(6));
let cv = av - bv;
assert_eq(cv, (-3, -3, -3));
assert_eq(cv * cv, (9, 9, 9));
Loading