{"record":{"id":"b573cc6d1a117cc4","repo":"GraphiteEditor/Graphite","slug":"handle-cannot-be-converted","errorCode":null,"errorMessage":"Handle cannot be converted","messagePattern":"Handle cannot be converted","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"editor/src/messages/tool/common_functionality/shape_editor.rs","lineNumber":2155,"sourceCode":"\t\t\tselect_segments,\n\t\t\tselect_points,\n\t\t\tselection_mode,\n\t\t);\n\n\t\tif selection_change == SelectionChange::Clear {\n\t\t\tself.deselect_all_points();\n\t\t\tself.deselect_all_segments();\n\t\t}\n\n\t\tfor (layer, points) in points_inside {\n\t\t\tlet Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };\n\n\t\t\tfor point in points {\n\t\t\t\tmatch (point, selection_change) {\n\t\t\t\t\t(_, SelectionChange::Shrink) => state.deselect_point(point),\n\t\t\t\t\t(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {\n\t\t\t\t\t\tlet handle = point.as_handle().expect(\"Handle cannot be converted\");\n\t\t\t\t\t\tif handle.length(&vector) > 0. {\n\t\t\t\t\t\t\tstate.select_point(point);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t(_, _) => state.select_point(point),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (layer, segments) in segments_inside {\n\t\t\tlet Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };\n\t\t\tmatch selection_change {\n\t\t\t\tSelectionChange::Shrink => segments.iter().for_each(|segment| state.deselect_segment(*segment)),\n\t\t\t\t_ => segments.iter().for_each(|segment| state.select_segment(*segment)),\n\t\t\t}\n\n\t\t\t// Also select/deselect the endpoints of respective segments\n\t\t\tlet Some(vector) = network_interface.compute_modified_vector(layer) else { continue };","sourceCodeStart":2137,"sourceCodeEnd":2173,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/tool/common_functionality/shape_editor.rs#L2137-L2173","documentation":"During box/lasso point selection, points matching ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) reach an arm that calls point.as_handle().expect(\"Handle cannot be converted\") (shape_editor.rs:2155). as_handle (vector-types/src/vector/misc.rs:520-526) returns Some only for the two handle variants and None for Anchor, so the preceding match guard makes this expect unreachable today. It becomes live the moment someone widens the arm (e.g. to (_, _)) or adds a new ManipulatorPointId variant without updating this match — an Anchor would then return None and panic.","triggerScenarios":"Regression edits: widening the match arm to cover Anchor points, adding a new ManipulatorPointId variant, or reordering arms so a non-handle point reaches the expect during a box/lasso selection drag.","commonSituations":"Copy-paste of the arm into a new match without the guard; enum extensions during feature work on manipulators; refactors that replace the match with an if that loses the narrowing.","solutions":["Replace the expect with if let Some(handle) = point.as_handle() inside the arm","Or match on point.as_handle() directly (Some(handle) => ..., None => fall through) so exhaustiveness is compiler-proven","If keeping the expect, add a comment tying it to the match guard and a debug_assert on the discriminant"],"exampleFix":"// before\n(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {\n\tlet handle = point.as_handle().expect(\"Handle cannot be converted\");\n\tif handle.length(&vector) > 0. { state.select_point(point); }\n}\n// after\n(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {\n\tif let Some(handle) = point.as_handle() {\n\t\tif handle.length(&vector) > 0. { state.select_point(point); }\n\t}\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Compiler-proven narrowing instead of expect:\nmatch point.as_handle() {\n\tSome(handle) if handle.length(&vector) > 0. => state.select_point(point),\n\t_ => state.select_point(point), // anchors and zero-length handles\n}","tryCatchPattern":null,"preventionTips":["Prefer pattern-matching on as_handle()/as_anchor() over expect guarded by a separate match arm","When adding a ManipulatorPointId variant, grep for as_handle()/as_anchor() expects and revisit each guard","Keep match arms narrow: (EndHandle|PrimaryHandle, _) must stay coupled to the as_handle() call inside it"],"tags":["rust","graphite","panic","expect","shape-editor","enum-narrowing","invariant"],"backgroundTag":"enum-variant-conversion-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}