Skip to content
Closed
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
14 changes: 11 additions & 3 deletions compiler/crates/hy-check/src/closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,14 +908,21 @@ fn check_stmt_boundaries<'a>(ck: &mut Checker<'_>, st: &mut FnState, stmt: &'a S
TypeShape::Chan(e) => *e,
_ => TypeShape::Unknown,
};
let elem_ty = ck.ty_of_expr(st, chan).and_then(|ct| {
match ck.resolve_type_alias(&ct) {
hy_syntax::TypeExpr::Chan { elem, .. } => Some(*elem),
_ => None,
}
});
for (i, n) in names.iter().enumerate() {
let shape = if i == 0 {
elem.clone()
} else {
TypeShape::Primitive
};
let ty = if i == 0 { elem_ty.clone() } else { None };
mark_nested_overwritten(st, n, nested);
ck.define_binding(st, n, shape, None, false, managed, false);
ck.define_binding(st, n, shape, ty, false, managed, false);
}
sync_nested_moves(st, nested);
}
Expand Down Expand Up @@ -987,11 +994,12 @@ fn check_stmt_boundaries<'a>(ck: &mut Checker<'_>, st: &mut FnState, stmt: &'a S
collect_expr_direct_fnlits(expr, st, nested);
let is_chan = matches!(ck.shape_of_expr(st, expr), TypeShape::Chan(_));
let shapes = ck.iterate_var_shapes(st, expr, vars.len());
for (name, shape) in vars.iter().zip(shapes) {
let tys = ck.iterate_var_tys(st, expr, vars.len());
for ((name, shape), ty) in vars.iter().zip(shapes).zip(tys) {
let is_ref = shape.is_reference_alias();
let may_share = ck.shape_may_share(&shape);
mark_nested_overwritten(st, name, nested);
ck.define_binding(st, name, shape, None, false, managed, false);
ck.define_binding(st, name, shape, ty, false, managed, false);
if !is_chan {
if may_share {
ck.record_may_share(st, name, expr);
Expand Down
20 changes: 17 additions & 3 deletions compiler/crates/hy-check/src/emit_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ impl<'a> Checker<'a> {
self.check_expr_emit_guard(st, expr);
if deep {
let shapes = self.iterate_var_shapes(st, expr, vars.len());
for (name, shape) in vars.iter().zip(shapes) {
self.define_binding(st, name, shape, None, false, false, false);
let tys = self.iterate_var_tys(st, expr, vars.len());
for ((name, shape), ty) in vars.iter().zip(shapes).zip(tys) {
self.define_binding(st, name, shape, ty, false, false, false);
}
}
}
Expand Down Expand Up @@ -321,13 +322,20 @@ impl<'a> Checker<'a> {
TypeShape::Chan(e) => *e,
_ => TypeShape::Unknown,
};
let elem_ty = self.ty_of_expr(st, chan).and_then(|ct| {
match self.resolve_type_alias(&ct) {
TypeExpr::Chan { elem, .. } => Some(*elem),
_ => None,
}
});
for (i, n) in names.iter().enumerate() {
let shape = if i == 0 {
elem.clone()
} else {
TypeShape::Primitive
};
self.define_binding(st, n, shape, None, false, false, false);
let ty = if i == 0 { elem_ty.clone() } else { None };
self.define_binding(st, n, shape, ty, false, false, false);
}
}
}
Expand Down Expand Up @@ -367,6 +375,12 @@ impl<'a> Checker<'a> {
let ty = l.ty.clone().or_else(|| self.ty_of_expr(st, val));
self.define_binding(st, name, shape, ty, immutable, false, is_let);
}
} else if l.values.len() == 1 && l.names.len() > 1 {
let shapes = self.multi_value_shapes(st, &l.values[0], l.names.len());
let tys = self.multi_value_tys(st, &l.values[0], l.names.len());
for ((name, shape), ty) in l.names.iter().zip(shapes).zip(tys) {
self.define_binding(st, name, shape, ty, immutable, false, is_let);
}
}
}

Expand Down
14 changes: 11 additions & 3 deletions compiler/crates/hy-check/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,13 +452,20 @@ fn check_fn(ck: &mut Checker<'_>, f: &FnDecl) {
TypeShape::Chan(e) => *e,
_ => TypeShape::Unknown,
};
let elem_ty = ck.ty_of_expr(&st, chan).and_then(|ct| {
match ck.resolve_type_alias(&ct) {
hy_syntax::TypeExpr::Chan { elem, .. } => Some(*elem),
_ => None,
}
});
for (i, n) in names.iter().enumerate() {
let shape = if i == 0 {
elem.clone()
} else {
TypeShape::Primitive
};
ck.define_binding(&mut st, n, shape, None, false, managed, false);
let ty = if i == 0 { elem_ty.clone() } else { None };
ck.define_binding(&mut st, n, shape, ty, false, managed, false);
}
}
walk::ScopeEvent::MatchBind { name, subject } => {
Expand Down Expand Up @@ -699,10 +706,11 @@ fn check_fn(ck: &mut Checker<'_>, f: &FnDecl) {
);
let is_chan = matches!(ck.shape_of_expr(&st, expr), TypeShape::Chan(_));
let shapes = ck.iterate_var_shapes(&st, expr, vars.len());
for (name, shape) in vars.iter().zip(shapes) {
let tys = ck.iterate_var_tys(&st, expr, vars.len());
for ((name, shape), ty) in vars.iter().zip(shapes).zip(tys) {
let is_ref = shape.is_reference_alias();
let may_share = ck.shape_may_share(&shape);
ck.define_binding(&mut st, name, shape, None, false, managed, false);
ck.define_binding(&mut st, name, shape, ty, false, managed, false);
if !is_chan {
if may_share {
ck.record_may_share(&mut st, name, expr);
Expand Down
28 changes: 19 additions & 9 deletions compiler/crates/hy-check/src/loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2067,7 +2067,9 @@ impl<'a> Checker<'a> {
if let Some(ty) = &b.ty {
let resolved = self.resolve_type_alias(ty);
match &resolved {
hy_syntax::TypeExpr::Array { .. } => return true,
hy_syntax::TypeExpr::Array { .. } | hy_syntax::TypeExpr::Option { .. } => {
return true
}
hy_syntax::TypeExpr::Named { name: tname, .. } => {
let base = Self::base_named_type_str(tname);
if self.structs.contains_key(base) {
Expand Down Expand Up @@ -5133,7 +5135,10 @@ impl<'a> Checker<'a> {
managed: bool,
) {
let shape = self.shape_of_expr(st, subject);
let ty = match self.ty_of_expr(st, subject) {
let ty = match self
.ty_of_expr(st, subject)
.map(|t| self.resolve_type_alias(&t))
{
Some(hy_syntax::TypeExpr::Option { elem, .. }) => Some(*elem),
other => other,
};
Expand Down Expand Up @@ -5551,15 +5556,18 @@ impl<'a> Checker<'a> {
}

if let Some(b) = st.env_lookup_mut(name) {
let keep_existing_option_ty = matches!(info.shape, TypeShape::Unknown)
&& b.ty.as_ref().is_some_and(|t| self.ty_is_option(t));
if !matches!(info.shape, TypeShape::Unknown) || b.ty.is_none() {
b.shape = info.shape;
}
if info.ty.is_some()
|| b.ty.is_none()
|| matches!(
b.ty.as_ref().map(|t| self.resolve_type_alias(t)),
Some(hy_syntax::TypeExpr::Fn { .. })
)
if !keep_existing_option_ty
&& (info.ty.is_some()
|| b.ty.is_none()
|| matches!(
b.ty.as_ref().map(|t| self.resolve_type_alias(t)),
Some(hy_syntax::TypeExpr::Fn { .. })
))
{
b.ty = info.ty;
}
Expand Down Expand Up @@ -5674,10 +5682,12 @@ impl<'a> Checker<'a> {
self.preserve_peers_before_sever(st, name, false);
}
if let Some(b) = st.env_lookup_mut(name) {
let keep_existing_option_ty = matches!(shape, TypeShape::Unknown)
&& b.ty.as_ref().is_some_and(|t| self.ty_is_option(t));
if !matches!(shape, TypeShape::Unknown) || b.ty.is_none() {
b.shape = shape.clone();
}
if ty.is_some() || b.ty.is_none() {
if !keep_existing_option_ty && (ty.is_some() || b.ty.is_none()) {
b.ty = ty;
}
b.moved = None;
Expand Down
19 changes: 19 additions & 0 deletions compiler/crates/hy-check/src/range_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,25 @@ impl<'a> Checker<'a> {
/// sides treat an unknown operand. A TYPE contract, not a sendability
/// doubt — `managed` does not suppress it.
pub(crate) fn check_range_iterable(&mut self, st: &FnState, expr: &Expr, span: Span) {
if self.expr_is_option(st, expr) {
let ty_str = self
.ty_of_expr(st, expr)
.map(|t| t.render())
.unwrap_or_else(|| "option".to_string());
let msg = format!(
"cannot range over `{}` (`{}`) — `for x in e` needs a slice, array, string, map, or channel, not an `option` wrapper (LX13)",
expr.render(),
ty_str
);
let notes = vec![
"why: `option[T]` lowers to a value struct in Go (`hyb_opt`), which Go cannot range over".to_string(),
"fix: unwrap the `option` with `match` (`match opt { some(xs) => for x in xs { ... }, none => {} }`) before ranging".to_string(),
];
self.diags.push(
Diagnostic::error("HYB-E2009", self.path.clone(), span, msg).with_notes(notes),
);
return;
}
// Go: "array, pointer to array, slice, string, map, or channel" —
// only pointer-to-ARRAY is rangeable (`*struct` / `*slice` /
// `*string` are not), so normalize exactly one Pointer level for
Expand Down
19 changes: 19 additions & 0 deletions compiler/crates/hy-check/src/recv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,25 @@ impl<'a> Checker<'a> {
/// side, an ill-typed source is not a sendability doubt — `managed`
/// does not suppress it.
pub(crate) fn check_recv_operand(&mut self, st: &FnState, chan: &Expr, span: Span) -> bool {
if self.expr_is_option(st, chan) {
let ty_str = self
.ty_of_expr(st, chan)
.map(|t| t.render())
.unwrap_or_else(|| "option".to_string());
let msg = format!(
"cannot receive from `{}` (`{}`) — a receive source must be a channel, not an `option` wrapper (O4)",
chan.render(),
ty_str
);
let notes = vec![
"why: `option[chan T]` lowers to a value struct in Go (`hyb_opt`), not a channel; receiving directly from an `option` fails `go build`".to_string(),
"fix: unwrap the `option` with `match` (`match opt { some(ch) => <-ch, none => {} }`) before receiving".to_string(),
];
self.diags.push(
Diagnostic::error("HYB-E2008", self.path.clone(), span, msg).with_notes(notes),
);
return false;
}
match self.shape_of_expr(st, chan) {
TypeShape::Chan(_) => {
if let Some(ty) = self.recv_chan_type_expr(st, chan) {
Expand Down
29 changes: 29 additions & 0 deletions compiler/crates/hy-check/src/sendability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,16 @@ impl<'a> Checker<'a> {
}),
});
}
if self.expr_is_option(st, operand) {
return Outcome::Rejected(RejectInfo {
value: expr.render(),
ty: format!("*{}", self.binding_ty_str(b)),
notes: vec![format!(
"`&{}` lends a mutable `option` wrapper — a live borrow of mutable shared state, which is not one of the sendable classes (O4)",
root
)],
});
}
match &b.shape {
// `&hits` where hits is atomic-typed — the mediated route (OQ6)
TypeShape::Atomic => Outcome::Ok,
Expand Down Expand Up @@ -832,6 +842,25 @@ impl<'a> Checker<'a> {
/// usual); false emits the diagnostic and the caller skips the value
/// check (the site is ill-typed — no cascade).
pub(crate) fn check_send_target(&mut self, st: &FnState, chan: &Expr, span: Span) -> bool {
if self.expr_is_option(st, chan) {
let ty_str = self
.ty_of_expr(st, chan)
.map(|t| t.render())
.unwrap_or_else(|| "option".to_string());
let msg = format!(
"cannot send to `{}` (`{}`) — a send target must be a channel, not an `option` wrapper (O4)",
chan.render(),
ty_str
);
let notes = vec![
"why: `option[chan T]` lowers to a value struct in Go (`hyb_opt`), not a channel; sending directly to an `option` fails `go build`".to_string(),
"fix: unwrap the `option` with `match` (`match opt { some(ch) => ch <- v, none => {} }`) before sending".to_string(),
];
self.diags.push(
Diagnostic::error("HYB-E3007", self.path.clone(), span, msg).with_notes(notes),
);
return false;
}
match self.shape_of_expr(st, chan) {
TypeShape::Chan(_) => {
if let Some(ty) = self.ty_of_expr(st, chan) {
Expand Down
Loading
Loading