GraphiteEditor/Graphite · warning
If `selection_shape` is a polygon then subpath is…
Error message
If `selection_shape` is a polygon then subpath is constructed beforehand.
What it means
select_intersecting_points builds polygon_subpath only when selection_shape is SelectionShape::Lasso (shape_editor.rs:2223-2231) and later unwraps it with .expect("If `selection_shape` is a polygon then subpath is constructed beforehand.") inside the Lasso arm of the segment-selection match (2256). The invariant 'Lasso implies Some(polygon)' holds only because the construction if-let and every access arm stay in lockstep — the compiler does not enforce it. Any edit that decouples them (new SelectionShape variant, access moved before construction, changed construction condition) panics on every lasso drag over segments.
Solutions
- Restructure so the polygon is passed by reference into lasso-only code paths (e.g. compute the predicate via a closure taking &Subpath<PointId>), removing the Option entirely
- Keep construction and all accesses driven by one match on selection_shape so adding a variant is a compile error, not a runtime panic
- As a minimal fix, convert the expect to let-else that skips the segment instead of panicking
Example fix
// before
SelectionShape::Lasso(_) => {
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
...
}
// after — pass the polygon in, no Option to unwrap
SelectionShape::Lasso(polygon) => {
let polygon_subpath = Subpath::<PointId>::from_anchors(polygon.to_vec(), true);
// polygon_subpath is now locally, provably constructed
...
} Defensive patterns
Strategy: validation
Validate before calling
// Make the invariant structural: derive polygon and access from one exhaustive match
let polygon_subpath = match selection_shape {
SelectionShape::Lasso(polygon) if polygon.len() >= 2 => Some(Subpath::<PointId>::from_anchors(polygon.to_vec(), true)),
SelectionShape::Lasso(_) => return (points_inside, segments_inside),
SelectionShape::Box(_) => None,
}; Prevention
- Bind construction and access to a single exhaustive match on the enum so new variants are compile errors
- Prefer passing precomputed data (the Subpath) into the code that needs it over storing an Optional alongside
- Add debug_asserts after constructing such precomputed state to catch drift early in development builds
When it happens
Trigger: Regression edits: adding a SelectionShape variant whose arm reads polygon_subpath, moving the segment loop above the construction, or altering the polygon.len() < 2 early-return so a Lasso arm executes with None.
Common situations: Extending selection shapes (e.g. adding a lasso-variant or brush shape); refactoring the intersection code into helpers that lose the construction context; changing the Subpath construction to be lazy/conditional.
Related errors
- Cannot find state for layer
- Handle cannot be converted
- In `check_layer()`: there should be a `target`
- No anchor position for handle
- No handle position
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/1e85fe214846d46a.
Report an issue: GitHub.
Appendix: source
Thrown at editor/src/messages/tool/common_functionality/shape_editor.rs:2256
let segment_bbox = transformed_segment.bounding_box();
let select = match selection_shape {
SelectionShape::Box(rect) => {
let enclosed = rect.contains_rect(segment_bbox);
match selection_mode {
SelectionMode::Enclosed => enclosed,
_ => {
// Check for intersection with the segment
enclosed
|| rect
.path_segments(DEFAULT_ACCURACY)
.map(|seg| seg.as_line().unwrap())
.any(|line| !transformed_segment.intersect_line(line).is_empty())
}
}
}
SelectionShape::Lasso(_) => {
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
// Sample 10 points on the bezier and check if all or some lie inside the polygon
let points = pathseg_compute_lookup_table(segment, Some(10), false);
match selection_mode {
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
}
}
};
if select {
segments_inside.entry(layer).or_default().insert(id);
}
}
let segment_points = pathseg_points(segment);
// Selecting handlesView on GitHub (pinned to c507b35645)