Skip to content

Commit d240798

Browse files
committed
Fix: detect multiple nullable ADT trait dispatch ambiguity
When multiple nullable ADT types implement the same trait method, their null (none) variants are bare null pointers with no tag to distinguish them. The dispatcher used nullable_adt_impls[0] for all null values, which could dispatch to the wrong impl. check_ambiguous_dynamic_dispatch now reports a compile error when multiple nullable ADTs implement the same trait method, since the ambiguity can't be resolved at runtime.
1 parent 4882501 commit d240798

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

crates/knot-compiler/src/codegen.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13133,6 +13133,53 @@ impl Codegen {
1313313133
.filter(|(_, types)| types.len() > 1)
1313413134
.collect();
1313513135
clashes.sort();
13136+
13137+
// Also check for multiple nullable ADT impls: their null (none)
13138+
// variants are bare null pointers with no tag to distinguish them,
13139+
// so dispatching a null value to the correct impl is impossible.
13140+
// The constructor-name overlap check above does NOT catch this
13141+
// because different nullable ADTs have different constructor names
13142+
// (e.g. `NothingA` vs `NothingB`).
13143+
let nullable_impl_types: Vec<&str> = info
13144+
.impls
13145+
.iter()
13146+
.filter_map(|e| {
13147+
let ctors = self.data_constructors.get(&e.type_name)?;
13148+
let is_nullable = ctors
13149+
.iter()
13150+
.any(|c| self.nullable_ctors.contains_key(c));
13151+
if is_nullable { Some(e.type_name.as_str()) } else { None }
13152+
})
13153+
.collect();
13154+
if nullable_impl_types.len() > 1 {
13155+
let trait_name = self
13156+
.trait_method_traits
13157+
.get(&method)
13158+
.cloned()
13159+
.unwrap_or_default();
13160+
diags.push(
13161+
knot::diagnostic::Diagnostic::error(format!(
13162+
"cannot dispatch '{}' at run time: multiple nullable ADT types \
13163+
({}) implement '{}', and their null values are indistinguishable",
13164+
method,
13165+
nullable_impl_types
13166+
.iter()
13167+
.map(|t| format!("'{}'", t))
13168+
.collect::<Vec<_>>()
13169+
.join(" and "),
13170+
trait_name,
13171+
))
13172+
.label(
13173+
span,
13174+
format!(
13175+
"this call is polymorphic, so '{}' has no static type here",
13176+
method
13177+
),
13178+
),
13179+
);
13180+
continue;
13181+
}
13182+
1313613183
let Some((ctor, types)) = clashes.first() else {
1313713184
continue;
1313813185
};

0 commit comments

Comments
 (0)