Skip to content

Commit 34ab6dc

Browse files
authored
Merge pull request #83 from ilyakooo0/push-mukurrouztqr
fix: trait default-body placeholder overwrites real signature, breaking deriving
2 parents a3441a1 + 978348b commit 34ab6dc

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

crates/knot-compiler/src/infer.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9026,11 +9026,20 @@ impl Infer {
90269026

90279027
for item in items {
90289028
if let ast::TraitItem::Method { name, ty, .. } = item {
9029-
// Skip default-body entries with placeholder types
9030-
if let ast::TypeKind::Named(n) = &ty.ty.node
9031-
&& n == "_" {
9032-
continue;
9033-
}
9029+
// Skip default-body entries with placeholder types.
9030+
// The parser emits these with `TypeKind::Hole` (not
9031+
// `Named("_")`), so the old guard never matched and the
9032+
// placeholder overwrote the real signature — pinning the
9033+
// method to a single unquantified type variable shared
9034+
// across all call sites (first use wins, second fails).
9035+
let is_placeholder = match &ty.ty.node {
9036+
ast::TypeKind::Hole => true,
9037+
ast::TypeKind::Named(n) => n == "_",
9038+
_ => false,
9039+
};
9040+
if is_placeholder {
9041+
continue;
9042+
}
90349043
self.annotation_vars.clear();
90359044
self.annotation_unit_vars.clear();
90369045
self.in_type_annotation = true;

crates/knot-compiler/tests/regress_knot_issues.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,3 +625,60 @@ fn refinements_nested_in_route_body_fields_are_validated() {
625625
let bad_top = r#"{"events":[],"top":"BAD"}"#;
626626
assert_eq!(post_json(port, "/gossip", bad_top).0, 400, "a top-level refined field");
627627
}
628+
629+
// ── 13. Trait default-body placeholder overwrites real signature ────
630+
631+
/// A trait method with both a signature and a default body caused the
632+
/// placeholder entry (TypeKind::Hole) to overwrite the real signature,
633+
/// pinning the method to a single unquantified type variable. The first
634+
/// call site bound it; the second failed with "type mismatch".
635+
#[test]
636+
fn trait_default_body_does_not_pin_method_type() {
637+
let src = r#"trait Greet a where
638+
greet : a -> Text
639+
greet x = "hi"
640+
data Dog = Dog {}
641+
data Cat = Cat {}
642+
impl Greet Dog where
643+
impl Greet Cat where
644+
main = do
645+
println (greet (Dog {}))
646+
println (greet (Cat {}))
647+
yield {}
648+
"#;
649+
let c = compile("trait_default_monomorphic", src);
650+
let out = Command::new(&c.exe)
651+
.current_dir(&c.dir)
652+
.output()
653+
.expect("failed to run compiled program");
654+
let stdout = String::from_utf8_lossy(&out.stdout);
655+
assert!(
656+
stdout.contains("\"hi\"") && stdout.matches("\"hi\"").count() == 2,
657+
"both impls should use the default body, got: {stdout}",
658+
);
659+
}
660+
661+
/// `deriving` for a trait with a default body must work at multiple types.
662+
#[test]
663+
fn deriving_works_at_multiple_types() {
664+
let src = r#"trait Describe a where
665+
describe : a -> Text
666+
describe x = "value: " ++ show x
667+
data Priority = Low {} | High {} deriving (Describe)
668+
data Color = Red {} | Blue {} deriving (Describe)
669+
main = do
670+
println (describe (Low {}))
671+
println (describe (Red {}))
672+
yield {}
673+
"#;
674+
let c = compile("deriving_multi_type", src);
675+
let out = Command::new(&c.exe)
676+
.current_dir(&c.dir)
677+
.output()
678+
.expect("failed to run compiled program");
679+
let stdout = String::from_utf8_lossy(&out.stdout);
680+
assert!(
681+
stdout.contains("Low") && stdout.contains("Red"),
682+
"both derived impls should work, got: {stdout}",
683+
);
684+
}

0 commit comments

Comments
 (0)