GraphiteEditor/Graphite · warning

Handle cannot be converted

Error message

Handle cannot be converted

What it means

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.

Source

Thrown at editor/src/messages/tool/common_functionality/shape_editor.rs:2155

			select_segments,
			select_points,
			selection_mode,
		);

		if selection_change == SelectionChange::Clear {
			self.deselect_all_points();
			self.deselect_all_segments();
		}

		for (layer, points) in points_inside {
			let Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };
			let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };

			for point in points {
				match (point, selection_change) {
					(_, SelectionChange::Shrink) => state.deselect_point(point),
					(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {
						let handle = point.as_handle().expect("Handle cannot be converted");
						if handle.length(&vector) > 0. {
							state.select_point(point);
						}
					}
					(_, _) => state.select_point(point),
				}
			}
		}

		for (layer, segments) in segments_inside {
			let Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };
			match selection_change {
				SelectionChange::Shrink => segments.iter().for_each(|segment| state.deselect_segment(*segment)),
				_ => segments.iter().for_each(|segment| state.select_segment(*segment)),
			}

			// Also select/deselect the endpoints of respective segments
			let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };

View on GitHub (pinned to c507b35645)

Solutions

  1. Replace the expect with if let Some(handle) = point.as_handle() inside the arm
  2. Or match on point.as_handle() directly (Some(handle) => ..., None => fall through) so exhaustiveness is compiler-proven
  3. If keeping the expect, add a comment tying it to the match guard and a debug_assert on the discriminant

Example fix

// before
(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {
	let handle = point.as_handle().expect("Handle cannot be converted");
	if handle.length(&vector) > 0. { state.select_point(point); }
}
// after
(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {
	if let Some(handle) = point.as_handle() {
		if handle.length(&vector) > 0. { state.select_point(point); }
	}
}
Defensive patterns

Strategy: type-guard

Type guard

// Compiler-proven narrowing instead of expect:
match point.as_handle() {
	Some(handle) if handle.length(&vector) > 0. => state.select_point(point),
	_ => state.select_point(point), // anchors and zero-length handles
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/b573cc6d1a117cc4. Report an issue: GitHub.