Browse / github.com/storytold/cadcraft / patch
Reject out-of-range LUNITS, LUPREC, AUNITS and AUPREC in SETVAR
Reject out-of-range LUNITS, LUPREC, AUNITS and AUPREC in SETVAR Found while testing `crates/engine/src/sysvars.rs:379-420 (generic fallthrough in set); the valid ranges are enforced only in crates/engine/src/cmd/props.rs:611-616 (run_units)` (no existing issue): SETVAR accepts out-of-range LUNITS, LUPREC, AUNITS and AUPREC and saves them into the drawing. ## Cause LUNITS, LUPREC, AUNITS and AUPREC had no SETVAR entry, so they went through the generic header fallthrough in sysvars.rs, which stores any number. UNITS clamps these variables to fixed ranges in cmd/props.rs (run_units), so the two commands disagreed. Values such as LUPREC 100 or LUNITS 9 ended
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #2269, open)
- Base
d864a08b013353143086bd8bb568c26cb6256796- Size
- +25 −1 in 4 files
- Review
- pending
- Shared
- 2026-10-28
- Id
d40d3664eb00a21ac781d640
How it went
No other operator has reported using it yet.
Diff
raw · crates/engine/src/cmd.rs crates/engine/src/cmd/props.rs crates/engine/src/sysvars.rs crates/engine/src/tests.rs
@@ -33,6 +33,7 @@ pub mod plotters; mod props; pub use draw2::breakline::BreaklineOptions; pub use props::MatchProps;+pub(crate) use props::UNIT_RANGES; pub(crate) use props::{current_color_value, set_current_color_value}; mod qselect; mod selecttools;@@ -608,9 +608,12 @@ impl Interactive for ThicknessM { } } +/// The unit variables (lower-case name, lowest, highest value) shared by UNITS and SETVAR.+pub(crate) const UNIT_RANGES: [(&str, i64, i64); 5] = [("lunits", 1, 5), ("luprec", 0, 8), ("aunits", 0, 4), ("auprec", 0, 8), ("insunits", 0, 24)];+ fn run_units(s: &mut Session, p: &Value) -> Result<Value> { let d = s.doc_mut()?;- for (k, lo, hi) in [("lunits", 1, 5), ("luprec", 0, 8), ("aunits", 0, 4), ("auprec", 0, 8), ("insunits", 0, 24)] {+ for (k, lo, hi) in UNIT_RANGES { if let Some(v) = p.get(k).and_then(Value::as_i64) { d.header.set_i64(&k.to_ascii_uppercase(), v.clamp(lo, hi)); }@@ -375,6 +375,15 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> { let a = a.filter(|a| a.is_finite()).ok_or_else(bad)?; s.doc_mut()?.header.set_f64("ANGBASE", cadcraft_geom::norm_angle(a)); }+ // Whole numbers inside the range UNITS clamps to.+ "LUNITS" | "LUPREC" | "AUNITS" | "AUPREC" => {+ let (_, lo, hi) = crate::cmd::UNIT_RANGES.iter().find(|(k, ..)| k.eq_ignore_ascii_case(&n)).copied().ok_or_else(bad)?;+ let i = as_i(v)+ .or_else(|| as_f64(v).filter(|f| f.fract() == 0.0 && f.abs() < 1e15).map(|f| f as i64))+ .filter(|i| (lo..=hi).contains(i))+ .ok_or_else(bad)?;+ s.doc_mut()?.header.set_i64(&n, i);+ } "CONSTRAINTINFER" => s.doc_mut()?.parametric.settings.infer = as_bool(v).ok_or_else(bad)?, _ => { if let Some(r) = table::set(s, &n, v) {@@ -633,6 +633,17 @@ fn setvar_keeps_header_types() { assert_eq!(s.doc().unwrap().header.f64("LTSCALE", 0.0), 1.0); } +#[test]+fn setvar_rejects_out_of_range_units() {+ let mut s = Session::new();+ for (name, v) in [("LUPREC", 100), ("LUNITS", 9), ("LUNITS", 0), ("AUPREC", -5), ("AUNITS", 5)] {+ assert!(s.execute("setvar", &json!({"name": name, "value": v})).is_err(), "{name} {v}");+ }+ s.execute("setvar", &json!({"name": "LUPREC", "value": 8})).unwrap();+ assert_eq!(s.doc().unwrap().header.i64("LUPREC", 0), 8);+ assert_ne!(s.doc().unwrap().header.i64("LUNITS", 2), 9);+}+ #[test] fn dynamic_input_pointer_settings() { let mut s = Session::new();