GraphiteEditor/Graphite · warning
If `selection_shape` is a polygon then subpath is constructe
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.
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)
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
- Handle cannot be converted
- Cannot find state for layer
- 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.