diff --git a/compiler/crates/hy-check/src/emit_guard.rs b/compiler/crates/hy-check/src/emit_guard.rs index e31214fe9..4b7e4a942 100644 --- a/compiler/crates/hy-check/src/emit_guard.rs +++ b/compiler/crates/hy-check/src/emit_guard.rs @@ -1310,7 +1310,9 @@ impl<'a> Checker<'a> { // TypeRef, so running the builtin-argument check here covers struct // fields, aliases, local annotations, literals and conversions. let empty_st = crate::model::FnState::default(); - self.check_expr_builtin_args(st.unwrap_or(&empty_st), len); + let st_ref = st.unwrap_or(&empty_st); + self.check_expr_recv_operands(st_ref, len); + self.check_expr_builtin_args(st_ref, len); let is_wrapped_ident_int_conv = matches!( unwrap_parens(len), Expr::Call { callee, args, .. } diff --git a/compiler/crates/hy-check/src/recv.rs b/compiler/crates/hy-check/src/recv.rs index 58e712509..370eff30d 100644 --- a/compiler/crates/hy-check/src/recv.rs +++ b/compiler/crates/hy-check/src/recv.rs @@ -199,7 +199,7 @@ impl<'a> Checker<'a> { /// Recurse an expression tree, classifying every `Expr::Receive` /// operand found at any depth. FnLit bodies are the A5 perimeter — /// not walked here (the closure pass enters them). - fn check_expr_recv_operands(&mut self, st: &FnState, expr: &Expr) { + pub(crate) fn check_expr_recv_operands(&mut self, st: &FnState, expr: &Expr) { match expr { Expr::Receive { chan, span } => { self.check_recv_operand(st, chan, *span); diff --git a/compiler/crates/hy-check/tests/recv_operand_soundness.rs b/compiler/crates/hy-check/tests/recv_operand_soundness.rs index 9c7bf2b33..955dcf709 100644 --- a/compiler/crates/hy-check/tests/recv_operand_soundness.rs +++ b/compiler/crates/hy-check/tests/recv_operand_soundness.rs @@ -196,3 +196,46 @@ fn drain(opt option[chan chan<- int]) { ); assert_has(&r_select, "HYB-E3008"); } + +#[test] +fn decl_type_expr_array_length_non_channel_receive_rejected_3880() { + // Issue #3880: struct field, type alias, enum payload, and fn signature + // array-length TypeExprs must reject receive from non-channel with HYB-E2008. + let r_struct = check( + r#"package main + +struct bad_struct { + a [<-5]int +} +"#, + ); + assert_has(&r_struct, "HYB-E2008"); + + let r_alias = check( + r#"package main + +type bad_alias = [<-"abc"]int +"#, + ); + assert_has(&r_alias, "HYB-E2008"); + + let r_fn_param = check( + r#"package main + +fn run(a [<-5]int) { + _ = a +} +"#, + ); + assert_has(&r_fn_param, "HYB-E2008"); + + let r_fn_ret = check( + r#"package main + +fn run() [<-5]int { + return [0] +} +"#, + ); + assert_has(&r_fn_ret, "HYB-E2008"); +}