{"record":{"id":"1e85fe214846d46a","repo":"GraphiteEditor/Graphite","slug":"if-selection-shape-is-a-polygon-then-subpath-is","errorCode":null,"errorMessage":"If `selection_shape` is a polygon then subpath is constructed beforehand.","messagePattern":"If `selection_shape` is a polygon then subpath is constructed beforehand\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"editor/src/messages/tool/common_functionality/shape_editor.rs","lineNumber":2256,"sourceCode":"\t\t\t\t\tlet segment_bbox = transformed_segment.bounding_box();\n\n\t\t\t\t\tlet select = match selection_shape {\n\t\t\t\t\t\tSelectionShape::Box(rect) => {\n\t\t\t\t\t\t\tlet enclosed = rect.contains_rect(segment_bbox);\n\t\t\t\t\t\t\tmatch selection_mode {\n\t\t\t\t\t\t\t\tSelectionMode::Enclosed => enclosed,\n\t\t\t\t\t\t\t\t_ => {\n\t\t\t\t\t\t\t\t\t// Check for intersection with the segment\n\t\t\t\t\t\t\t\t\tenclosed\n\t\t\t\t\t\t\t\t\t\t|| rect\n\t\t\t\t\t\t\t\t\t\t\t.path_segments(DEFAULT_ACCURACY)\n\t\t\t\t\t\t\t\t\t\t\t.map(|seg| seg.as_line().unwrap())\n\t\t\t\t\t\t\t\t\t\t\t.any(|line| !transformed_segment.intersect_line(line).is_empty())\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tSelectionShape::Lasso(_) => {\n\t\t\t\t\t\t\tlet polygon = polygon_subpath.as_ref().expect(\"If `selection_shape` is a polygon then subpath is constructed beforehand.\");\n\n\t\t\t\t\t\t\t// Sample 10 points on the bezier and check if all or some lie inside the polygon\n\t\t\t\t\t\t\tlet points = pathseg_compute_lookup_table(segment, Some(10), false);\n\t\t\t\t\t\t\tmatch selection_mode {\n\t\t\t\t\t\t\t\tSelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),\n\t\t\t\t\t\t\t\t_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif select {\n\t\t\t\t\t\tsegments_inside.entry(layer).or_default().insert(id);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet segment_points = pathseg_points(segment);\n\n\t\t\t\t// Selecting handles","sourceCodeStart":2238,"sourceCodeEnd":2274,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/tool/common_functionality/shape_editor.rs#L2238-L2274","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nSelectionShape::Lasso(_) => {\n\tlet polygon = polygon_subpath.as_ref().expect(\"If `selection_shape` is a polygon then subpath is constructed beforehand.\");\n\t...\n}\n// after — pass the polygon in, no Option to unwrap\nSelectionShape::Lasso(polygon) => {\n\tlet polygon_subpath = Subpath::<PointId>::from_anchors(polygon.to_vec(), true);\n\t// polygon_subpath is now locally, provably constructed\n\t...\n}","handlingStrategy":"validation","validationCode":"// Make the invariant structural: derive polygon and access from one exhaustive match\nlet polygon_subpath = match selection_shape {\n\tSelectionShape::Lasso(polygon) if polygon.len() >= 2 => Some(Subpath::<PointId>::from_anchors(polygon.to_vec(), true)),\n\tSelectionShape::Lasso(_) => return (points_inside, segments_inside),\n\tSelectionShape::Box(_) => None,\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","graphite","panic","expect","shape-editor","lasso-selection","invariant"],"backgroundTag":"optional-invariant-violated","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}