@@ -12867,16 +12867,27 @@ impl Codegen {
1286712867 if let Some(&v) = env.bindings.get(name.as_str()) {
1286812868 v
1286912869 } else {
12870- let var_expr = ast::Spanned::new(
12871- ast::ExprKind::Var(name.clone()),
12872- ast::Span::new(0, 0),
12873- );
12874- self.compile_expr(builder, &var_expr, env, db)
12870+ // Check let_bindings first — a do-local let variable
12871+ // would panic if resolved through compile_expr as a
12872+ // top-level function.
12873+ let let_expr = self.let_bindings.get(name).cloned();
12874+ if let Some(let_expr) = let_expr {
12875+ self.compile_expr(builder, &let_expr, env, db)
12876+ } else {
12877+ let var_expr = ast::Spanned::new(
12878+ ast::ExprKind::Var(name.clone()),
12879+ ast::Span::new(0, 0),
12880+ );
12881+ self.compile_expr(builder, &var_expr, env, db)
12882+ }
1287512883 }
1287612884 }
1287712885 SqlParamSource::FieldAccess(var, field) => {
12886+ let let_expr = self.let_bindings.get(var).cloned();
1287812887 let record = if let Some(&v) = env.bindings.get(var.as_str()) {
1287912888 v
12889+ } else if let Some(let_expr) = let_expr {
12890+ self.compile_expr(builder, &let_expr, env, db)
1288012891 } else {
1288112892 let var_expr = ast::Spanned::new(
1288212893 ast::ExprKind::Var(var.clone()),
@@ -17527,26 +17538,33 @@ fn extract_literal(expr: &ast::Expr) -> Option<CompileLit> {
1752717538/// satisfied, `Some(false)` if it fails, or `None` if it can't be
1752817539/// evaluated at compile time.
1752917540fn eval_refine_predicate(pred: &ast::Expr, lit: &CompileLit) -> Option<bool> {
17530- // The predicate is a lambda `\x -> body`. Extract the body.
17531- let body = match &pred.node {
17541+ // The predicate is a lambda `\\x -> body`. Extract the parameter name
17542+ // and body so we only substitute for the actual parameter, not any
17543+ // other variable the predicate may reference (e.g. a top-level constant).
17544+ let (param_name, body) = match &pred.node {
1753217545 ast::ExprKind::Lambda { params, body } => {
1753317546 if params.len() != 1 { return None; }
17534- body
17547+ let name = match ¶ms[0].node {
17548+ ast::PatKind::Var(n) => n.clone(),
17549+ _ => return None,
17550+ };
17551+ (name, body)
1753517552 }
1753617553 _ => return None,
1753717554 };
1753817555 // Evaluate the body with the parameter bound to the literal.
17539- eval_expr_bool(body, lit)
17556+ eval_expr_bool(body, lit, ¶m_name )
1754017557}
1754117558
1754217559/// Evaluate an expression to a boolean, with the refinement variable
17543- /// bound to `lit`. Returns `None` if the expression can't be evaluated.
17544- fn eval_expr_bool(expr: &ast::Expr, lit: &CompileLit) -> Option<bool> {
17560+ /// `param_name` bound to `lit`. Returns `None` if the expression can't be
17561+ /// evaluated (e.g. references a variable other than `param_name`).
17562+ fn eval_expr_bool(expr: &ast::Expr, lit: &CompileLit, param_name: &str) -> Option<bool> {
1754517563 match &expr.node {
1754617564 ast::ExprKind::Lit(ast::Literal::Bool(b)) => Some(*b),
1754717565 ast::ExprKind::BinOp { op, lhs, rhs, .. } => {
17548- let lv = eval_expr_num(lhs, lit)?;
17549- let rv = eval_expr_num(rhs, lit)?;
17566+ let lv = eval_expr_num(lhs, lit, param_name )?;
17567+ let rv = eval_expr_num(rhs, lit, param_name )?;
1755017568 match op {
1755117569 ast::BinOp::Lt => Some(lv < rv),
1755217570 ast::BinOp::Gt => Some(lv > rv),
@@ -17555,40 +17573,46 @@ fn eval_expr_bool(expr: &ast::Expr, lit: &CompileLit) -> Option<bool> {
1755517573 ast::BinOp::Eq => Some(lv == rv),
1755617574 ast::BinOp::Neq => Some(lv != rv),
1755717575 ast::BinOp::And => {
17558- Some(eval_expr_bool(lhs, lit)? && eval_expr_bool(rhs, lit)?)
17576+ Some(eval_expr_bool(lhs, lit, param_name )? && eval_expr_bool(rhs, lit, param_name )?)
1755917577 }
1756017578 ast::BinOp::Or => {
17561- Some(eval_expr_bool(lhs, lit)? || eval_expr_bool(rhs, lit)?)
17579+ Some(eval_expr_bool(lhs, lit, param_name )? || eval_expr_bool(rhs, lit, param_name )?)
1756217580 }
1756317581 _ => None,
1756417582 }
1756517583 }
1756617584 ast::ExprKind::UnaryOp { op: ast::UnaryOp::Not, operand, .. } => {
17567- Some(!eval_expr_bool(operand, lit)?)
17585+ Some(!eval_expr_bool(operand, lit, param_name )?)
1756817586 }
1756917587 _ => None,
1757017588 }
1757117589}
1757217590
1757317591/// Evaluate an expression to an f64 (for numeric comparisons), with the
17574- /// refinement variable bound to `lit`.
17575- fn eval_expr_num(expr: &ast::Expr, lit: &CompileLit) -> Option<f64> {
17592+ /// refinement variable `param_name` bound to `lit`. Returns `None` for any
17593+ /// variable that isn't the refinement parameter.
17594+ fn eval_expr_num(expr: &ast::Expr, lit: &CompileLit, param_name: &str) -> Option<f64> {
1757617595 match &expr.node {
1757717596 ast::ExprKind::Lit(ast::Literal::Int(s)) => s.parse::<f64>().ok(),
1757817597 ast::ExprKind::Lit(ast::Literal::Float(f)) => Some(*f),
17579- ast::ExprKind::Var(_) => {
17598+ ast::ExprKind::Var(name) if name == param_name => {
1758017599 // The lambda parameter — return the literal value
1758117600 match lit {
1758217601 CompileLit::Int(n) => Some(*n as f64),
1758317602 CompileLit::Float(f) => Some(*f),
1758417603 _ => None,
1758517604 }
1758617605 }
17587- ast::ExprKind::Annot { expr, .. } => eval_expr_num(expr, lit),
17588- ast::ExprKind::UnitLit { value, .. } => eval_expr_num(value, lit),
17606+ ast::ExprKind::Var(_) => {
17607+ // A different variable (e.g. a top-level constant) — can't
17608+ // evaluate at compile time, fall back to runtime check.
17609+ None
17610+ }
17611+ ast::ExprKind::Annot { expr, .. } => eval_expr_num(expr, lit, param_name),
17612+ ast::ExprKind::UnitLit { value, .. } => eval_expr_num(value, lit, param_name),
1758917613 ast::ExprKind::BinOp { op, lhs, rhs, .. } => {
17590- let lv = eval_expr_num(lhs, lit)?;
17591- let rv = eval_expr_num(rhs, lit)?;
17614+ let lv = eval_expr_num(lhs, lit, param_name )?;
17615+ let rv = eval_expr_num(rhs, lit, param_name )?;
1759217616 match op {
1759317617 ast::BinOp::Add => Some(lv + rv),
1759417618 ast::BinOp::Sub => Some(lv - rv),
0 commit comments