@@ -305,14 +305,15 @@ TEST_F(BinderTest, ErrorNestedUnboundField) {
305305class IsBoundVisitorTest : public ExpressionVisitorTest {};
306306
307307TEST_F (IsBoundVisitorTest, Constants) {
308- // True and False should error out
309308 auto true_expr = Expressions::AlwaysTrue ();
310309 auto result_true = IsBoundVisitor::IsBound (true_expr);
311- EXPECT_THAT (result_true, IsError (ErrorKind::kInvalidExpression ));
310+ ASSERT_THAT (result_true, IsOk ());
311+ EXPECT_FALSE (*result_true);
312312
313313 auto false_expr = Expressions::AlwaysFalse ();
314314 auto result_false = IsBoundVisitor::IsBound (false_expr);
315- EXPECT_THAT (result_false, IsError (ErrorKind::kInvalidExpression ));
315+ ASSERT_THAT (result_false, IsOk ());
316+ EXPECT_FALSE (*result_false);
316317}
317318
318319TEST_F (IsBoundVisitorTest, UnboundPredicate) {
@@ -322,6 +323,39 @@ TEST_F(IsBoundVisitorTest, UnboundPredicate) {
322323 EXPECT_FALSE (is_bound);
323324}
324325
326+ TEST_F (IsBoundVisitorTest, AndWithConstants) {
327+ auto unbound = Expressions::Equal (" name" , Literal::String (" Alice" ));
328+ ICEBERG_UNWRAP_OR_FAIL (auto bound, Bind (unbound));
329+
330+ // Use Make to keep constants in the expression tree instead of folding them.
331+ ICEBERG_UNWRAP_OR_FAIL (std::shared_ptr<Expression> unbound_and,
332+ And::Make (Expressions::AlwaysTrue (), unbound));
333+ ICEBERG_UNWRAP_OR_FAIL (auto unbound_result, IsBoundVisitor::IsBound (unbound_and));
334+ EXPECT_FALSE (unbound_result);
335+ EXPECT_THAT (Bind (unbound_and), IsOk ());
336+
337+ ICEBERG_UNWRAP_OR_FAIL (std::shared_ptr<Expression> bound_and,
338+ And::Make (bound, Expressions::AlwaysTrue ()));
339+ ICEBERG_UNWRAP_OR_FAIL (auto bound_result, IsBoundVisitor::IsBound (bound_and));
340+ EXPECT_TRUE (bound_result);
341+ }
342+
343+ TEST_F (IsBoundVisitorTest, OrWithConstants) {
344+ auto unbound = Expressions::Equal (" name" , Literal::String (" Alice" ));
345+ ICEBERG_UNWRAP_OR_FAIL (auto bound, Bind (unbound));
346+
347+ ICEBERG_UNWRAP_OR_FAIL (std::shared_ptr<Expression> unbound_or,
348+ Or::Make (Expressions::AlwaysFalse (), unbound));
349+ ICEBERG_UNWRAP_OR_FAIL (auto unbound_result, IsBoundVisitor::IsBound (unbound_or));
350+ EXPECT_FALSE (unbound_result);
351+ EXPECT_THAT (Bind (unbound_or), IsOk ());
352+
353+ ICEBERG_UNWRAP_OR_FAIL (std::shared_ptr<Expression> bound_or,
354+ Or::Make (bound, Expressions::AlwaysFalse ()));
355+ ICEBERG_UNWRAP_OR_FAIL (auto bound_result, IsBoundVisitor::IsBound (bound_or));
356+ EXPECT_TRUE (bound_result);
357+ }
358+
325359TEST_F (IsBoundVisitorTest, BoundPredicate) {
326360 // Bound predicates should return true
327361 auto unbound_pred = Expressions::Equal (" name" , Literal::String (" Alice" ));
@@ -342,14 +376,14 @@ TEST_F(IsBoundVisitorTest, AndWithBoundChildren) {
342376}
343377
344378TEST_F (IsBoundVisitorTest, AndWithUnboundChild) {
345- // AND with any unbound child should return false
379+ // Mixing bound and unbound predicates is invalid.
346380 auto bound_pred = Expressions::Equal (" name" , Literal::String (" Alice" ));
347381 ICEBERG_UNWRAP_OR_FAIL (auto pred1, Bind (bound_pred));
348382 auto pred2 = Expressions::Equal (" age" , Literal::Int (25 )); // unbound
349383 auto mixed_and = Expressions::And (pred1, pred2);
350384
351- ICEBERG_UNWRAP_OR_FAIL ( auto is_bound, IsBoundVisitor::IsBound (mixed_and));
352- EXPECT_FALSE (is_bound );
385+ EXPECT_THAT ( IsBoundVisitor::IsBound (mixed_and),
386+ HasErrorMessage ( " Found partially bound expression " ) );
353387}
354388
355389TEST_F (IsBoundVisitorTest, OrWithBoundChildren) {
@@ -364,14 +398,14 @@ TEST_F(IsBoundVisitorTest, OrWithBoundChildren) {
364398}
365399
366400TEST_F (IsBoundVisitorTest, OrWithUnboundChild) {
367- // OR with any unbound child should return false
401+ // Mixing bound and unbound predicates is invalid.
368402 auto pred1 = Expressions::IsNull (" name" ); // unbound
369403 auto bound_pred2 = Expressions::Equal (" age" , Literal::Int (25 ));
370404 ICEBERG_UNWRAP_OR_FAIL (auto pred2, Bind (bound_pred2));
371405 auto mixed_or = Expressions::Or (pred1, pred2);
372406
373- ICEBERG_UNWRAP_OR_FAIL ( auto is_bound, IsBoundVisitor::IsBound (mixed_or));
374- EXPECT_FALSE (is_bound );
407+ EXPECT_THAT ( IsBoundVisitor::IsBound (mixed_or),
408+ HasErrorMessage ( " Found partially bound expression " ) );
375409}
376410
377411TEST_F (IsBoundVisitorTest, NotWithBoundChild) {
@@ -405,15 +439,15 @@ TEST_F(IsBoundVisitorTest, ComplexExpression) {
405439 ICEBERG_UNWRAP_OR_FAIL (auto is_bound, IsBoundVisitor::IsBound (bound_complex));
406440 EXPECT_TRUE (is_bound);
407441
408- // Complex expression: one unbound should return false
442+ // Mixed binding state is rejected even in a nested expression.
409443 auto unbound_pred = Expressions::Equal (" name" , Literal::String (" Alice" ));
410444 ICEBERG_UNWRAP_OR_FAIL (auto bound_pred2, Bind (pred2));
411445 ICEBERG_UNWRAP_OR_FAIL (auto bound_pred3, Bind (pred3));
412446 auto mixed_and = Expressions::And (unbound_pred, bound_pred2);
413447 auto mixed_complex = Expressions::Or (mixed_and, bound_pred3);
414448
415- ICEBERG_UNWRAP_OR_FAIL ( auto is_bound_mixed, IsBoundVisitor::IsBound (mixed_complex));
416- EXPECT_FALSE (is_bound_mixed );
449+ EXPECT_THAT ( IsBoundVisitor::IsBound (mixed_complex),
450+ HasErrorMessage ( " Found partially bound expression " ) );
417451}
418452
419453class RewriteNotTest : public ExpressionVisitorTest {};
0 commit comments