Skip to content

Commit 4b8417b

Browse files
authored
Restore correct within() semantics (#293)
The previous change introduced a regression that flipped the behavior of within() by reusing is_in. is_in(self, other) implements the in operator: a in b → b.is_in(a) → "is a in b?" within(self, other) checks if self fits within other This change fixes within() by directly checking if self fits within other. Also, we should really use the "value in container" syntax as its standard starlark. within() is a bit bespoke
1 parent 4a16a59 commit 4b8417b

File tree

1 file changed

+106
-2
lines changed

1 file changed

+106
-2
lines changed

crates/pcb-sch/src/physical.rs

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,8 +1173,18 @@ fn physical_value_methods(methods: &mut MethodsBuilder) {
11731173
this: &PhysicalValue,
11741174
#[starlark(require = pos)] other: Value<'v>,
11751175
) -> starlark::Result<bool> {
1176-
// Thin wrapper around is_in
1177-
this.is_in(other)
1176+
// Check if this fits within other
1177+
let (other_min, other_max) = extract_bounds(other, this.unit)?;
1178+
1179+
let self_lower = this.value * (Decimal::ONE - this.tolerance);
1180+
let self_upper = this.value * (Decimal::ONE + this.tolerance);
1181+
let (self_min, self_max) = if self_lower < self_upper {
1182+
(self_lower, self_upper)
1183+
} else {
1184+
(self_upper, self_lower)
1185+
};
1186+
1187+
Ok(self_min >= other_min && self_max <= other_max)
11781188
}
11791189
}
11801190

@@ -4057,4 +4067,98 @@ mod tests {
40574067
let result = volts.downcast_ref::<PhysicalRange>().unwrap().is_in(amps);
40584068
assert!(result.is_err());
40594069
}
4070+
4071+
#[test]
4072+
fn test_within_method() {
4073+
use starlark::environment::Module;
4074+
use starlark::eval::Evaluator;
4075+
use starlark::values::Heap;
4076+
4077+
let module = Module::new();
4078+
let heap = module.heap();
4079+
let mut eval = Evaluator::new(&module);
4080+
4081+
// Test case from the regression: 4.7uF ±10% (house part) should fit within 4.7uF requirement
4082+
let house_part = heap.alloc(physical_value(4.7e-6, 0.1, PhysicalUnit::Farads)); // 4.7uF ±10%
4083+
let requirement = heap.alloc_str("4.7uF"); // 4.7uF (no tolerance = 0%)
4084+
4085+
// within() should check if house_part fits within requirement
4086+
let result = eval.eval_function(
4087+
house_part.get_attr("within", heap).unwrap().unwrap(),
4088+
&[requirement.to_value()],
4089+
&[],
4090+
);
4091+
assert!(result.is_ok());
4092+
assert_eq!(result.unwrap().unpack_bool(), Some(false)); // 10% tolerance doesn't fit in 0% tolerance
4093+
4094+
// Test case: tight tolerance fits within loose tolerance
4095+
let tight = heap.alloc(physical_value(5.5, 0.01, PhysicalUnit::Volts)); // 5.5V ±1%
4096+
let loose = heap.alloc_str("6V 10%"); // 6V ±10% = [5.4V, 6.6V]
4097+
4098+
let result = eval.eval_function(
4099+
tight.get_attr("within", heap).unwrap().unwrap(),
4100+
&[loose.to_value()],
4101+
&[],
4102+
);
4103+
assert!(result.is_ok());
4104+
assert_eq!(result.unwrap().unpack_bool(), Some(true)); // [5.445, 5.555] fits in [5.4, 6.6]
4105+
4106+
// Test case: loose tolerance doesn't fit within tight tolerance
4107+
let loose_val = heap.alloc(physical_value(6.0, 0.1, PhysicalUnit::Volts)); // 6V ±10%
4108+
let tight_val = heap.alloc_str("5.5V 1%"); // 5.5V ±1%
4109+
4110+
let result = eval.eval_function(
4111+
loose_val.get_attr("within", heap).unwrap().unwrap(),
4112+
&[tight_val.to_value()],
4113+
&[],
4114+
);
4115+
assert!(result.is_ok());
4116+
assert_eq!(result.unwrap().unpack_bool(), Some(false)); // [5.4, 6.6] doesn't fit in [5.445, 5.555]
4117+
}
4118+
4119+
#[test]
4120+
fn test_within_vs_is_in_semantics() {
4121+
use starlark::environment::Module;
4122+
use starlark::eval::Evaluator;
4123+
use starlark::values::Heap;
4124+
4125+
let module = Module::new();
4126+
let heap = module.heap();
4127+
let mut eval = Evaluator::new(&module);
4128+
4129+
// Create values: tight = 5.5V ±1%, loose = 6V ±10%
4130+
let tight = heap.alloc(physical_value(5.5, 0.01, PhysicalUnit::Volts));
4131+
let loose = heap.alloc(physical_value(6.0, 0.1, PhysicalUnit::Volts));
4132+
4133+
// tight.within(loose) should be true (tight fits in loose)
4134+
let within_result = eval
4135+
.eval_function(
4136+
tight.get_attr("within", heap).unwrap().unwrap(),
4137+
&[loose.to_value()],
4138+
&[],
4139+
)
4140+
.unwrap();
4141+
assert_eq!(within_result.unpack_bool(), Some(true));
4142+
4143+
// "tight in loose" (Starlark syntax) should also be true
4144+
// This calls loose.is_in(tight), checking if tight is in loose
4145+
let is_in_result = loose.downcast_ref::<PhysicalValue>().unwrap().is_in(tight);
4146+
assert!(is_in_result.is_ok());
4147+
assert_eq!(is_in_result.unwrap(), true);
4148+
4149+
// loose.within(tight) should be false (loose doesn't fit in tight)
4150+
let within_result2 = eval
4151+
.eval_function(
4152+
loose.get_attr("within", heap).unwrap().unwrap(),
4153+
&[tight.to_value()],
4154+
&[],
4155+
)
4156+
.unwrap();
4157+
assert_eq!(within_result2.unpack_bool(), Some(false));
4158+
4159+
// tight.is_in(loose) checks if loose is in tight, should be false
4160+
let is_in_result2 = tight.downcast_ref::<PhysicalValue>().unwrap().is_in(loose);
4161+
assert!(is_in_result2.is_ok());
4162+
assert_eq!(is_in_result2.unwrap(), false);
4163+
}
40604164
}

0 commit comments

Comments
 (0)