Skip to content

Commit aa6ed89

Browse files
committed
Raise completion errors
1 parent c34ea69 commit aa6ed89

5 files changed

Lines changed: 240 additions & 74 deletions

File tree

ext/rubydex/graph.c

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,15 @@ static VALUE rdxr_graph_diagnostics(VALUE self) {
490490
return diagnostics;
491491
}
492492

493-
// Helper: convert a CompletionCandidateArray into a Ruby array of typed objects and free the C array.
494-
static VALUE completion_candidates_to_ruby_array(CompletionCandidateArray *array, VALUE graph_obj) {
493+
// Helper: convert a CompletionResult into a Ruby array, raising ArgumentError on error.
494+
static VALUE completion_result_to_ruby_array(struct CompletionResult result, VALUE graph_obj) {
495+
if (result.error != NULL) {
496+
VALUE msg = rb_utf8_str_new_cstr(result.error);
497+
free_c_string(result.error);
498+
rb_raise(rb_eArgError, "%s", StringValueCStr(msg));
499+
}
500+
501+
CompletionCandidateArray *array = result.candidates;
495502
if (array == NULL) {
496503
return rb_ary_new();
497504
}
@@ -501,7 +508,7 @@ static VALUE completion_candidates_to_ruby_array(CompletionCandidateArray *array
501508
return rb_ary_new();
502509
}
503510

504-
VALUE result = rb_ary_new_capa((long)array->len);
511+
VALUE ruby_array = rb_ary_new_capa((long)array->len);
505512

506513
for (size_t i = 0; i < array->len; i++) {
507514
CCompletionCandidate item = array->items[i];
@@ -532,11 +539,11 @@ static VALUE completion_candidates_to_ruby_array(CompletionCandidateArray *array
532539
rb_raise(rb_eRuntimeError, "Unknown CCompletionCandidateKind: %d", item.kind);
533540
}
534541

535-
rb_ary_push(result, obj);
542+
rb_ary_push(ruby_array, obj);
536543
}
537544

538545
rdx_completion_candidates_free(array);
539-
return result;
546+
return ruby_array;
540547
}
541548

542549
// Graph#complete_expression: (Array[String] nesting) -> Array[Declaration | Keyword]
@@ -551,11 +558,11 @@ static VALUE rdxr_graph_complete_expression(VALUE self, VALUE nesting) {
551558
size_t nesting_count = RARRAY_LEN(nesting);
552559
char **converted_nesting = rdxi_str_array_to_char(nesting, nesting_count);
553560

554-
CompletionCandidateArray *results =
561+
struct CompletionResult result =
555562
rdx_graph_complete_expression(graph, (const char *const *)converted_nesting, nesting_count);
556563

557564
rdxi_free_str_array(converted_nesting, nesting_count);
558-
return completion_candidates_to_ruby_array(results, self);
565+
return completion_result_to_ruby_array(result, self);
559566
}
560567

561568
// Graph#complete_namespace_access: (String name) -> Array[Declaration]
@@ -566,8 +573,8 @@ static VALUE rdxr_graph_complete_namespace_access(VALUE self, VALUE name) {
566573
void *graph;
567574
TypedData_Get_Struct(self, void *, &graph_type, graph);
568575

569-
CompletionCandidateArray *results = rdx_graph_complete_namespace_access(graph, StringValueCStr(name));
570-
return completion_candidates_to_ruby_array(results, self);
576+
struct CompletionResult result = rdx_graph_complete_namespace_access(graph, StringValueCStr(name));
577+
return completion_result_to_ruby_array(result, self);
571578
}
572579

573580
// Graph#complete_method_call: (String name) -> Array[Declaration]
@@ -578,8 +585,8 @@ static VALUE rdxr_graph_complete_method_call(VALUE self, VALUE name) {
578585
void *graph;
579586
TypedData_Get_Struct(self, void *, &graph_type, graph);
580587

581-
CompletionCandidateArray *results = rdx_graph_complete_method_call(graph, StringValueCStr(name));
582-
return completion_candidates_to_ruby_array(results, self);
588+
struct CompletionResult result = rdx_graph_complete_method_call(graph, StringValueCStr(name));
589+
return completion_result_to_ruby_array(result, self);
583590
}
584591

585592
// Graph#complete_method_argument: (String name, Array[String] nesting) -> Array[Declaration | Keyword | KeywordParameter]
@@ -594,11 +601,11 @@ static VALUE rdxr_graph_complete_method_argument(VALUE self, VALUE name, VALUE n
594601
size_t nesting_count = RARRAY_LEN(nesting);
595602
char **converted_nesting = rdxi_str_array_to_char(nesting, nesting_count);
596603

597-
CompletionCandidateArray *results = rdx_graph_complete_method_argument(
604+
struct CompletionResult result = rdx_graph_complete_method_argument(
598605
graph, StringValueCStr(name), (const char *const *)converted_nesting, nesting_count);
599606

600607
rdxi_free_str_array(converted_nesting, nesting_count);
601-
return completion_candidates_to_ruby_array(results, self);
608+
return completion_result_to_ruby_array(result, self);
602609
}
603610

604611
void rdxi_initialize_graph(VALUE moduleRubydex) {

rbi/rubydex.rbi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,17 +248,17 @@ class Rubydex::Graph
248248
#
249249
# The nesting array represents the lexical scope stack, where the last element is the self type. An empty array
250250
# defaults to `Object` as the self type (top-level context).
251-
sig { params(nesting: T::Array[String]).returns(T::Array[T.any(Rubydex::Declaration, Rubydex::Keyword, Rubydex::KeywordParameter)]) }
251+
sig { params(nesting: T::Array[String]).returns(T::Array[T.any(Rubydex::Declaration, Rubydex::Keyword)]) }
252252
def complete_expression(nesting); end
253253

254254
# Returns completion candidates after a namespace access operator (e.g., `Foo::`). This includes all constants and
255255
# singleton methods for the namespace and its ancestors.
256-
sig { params(name: String).returns(T::Array[T.any(Rubydex::Declaration, Rubydex::Keyword, Rubydex::KeywordParameter)]) }
256+
sig { params(name: String).returns(T::Array[Rubydex::Declaration]) }
257257
def complete_namespace_access(name); end
258258

259259
# Returns completion candidates after a method call operator (e.g., `foo.`). This includes all methods that exist on
260260
# the type of the receiver and its ancestors.
261-
sig { params(name: String).returns(T::Array[T.any(Rubydex::Declaration, Rubydex::Keyword, Rubydex::KeywordParameter)]) }
261+
sig { params(name: String).returns(T::Array[Rubydex::Method]) }
262262
def complete_method_call(name); end
263263

264264
# Returns completion candidates inside a method call's argument list (e.g., `foo.bar(|)`). This includes everything

rust/rubydex-sys/src/graph_api.rs

Lines changed: 91 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -715,60 +715,104 @@ unsafe fn completion_nesting_name_id(
715715
Ok(name_api::nesting_stack_to_name_id(graph, &self_name, nesting))
716716
}
717717

718-
/// Runs completion for the given receiver and returns a structured array of candidates
718+
/// The result of a completion operation, carrying either a candidate array or an error message.
719+
#[repr(C)]
720+
pub struct CompletionResult {
721+
/// Non-null on success; null on error.
722+
pub candidates: *mut CompletionCandidateArray,
723+
/// Non-null on error; null on success. Caller must free with `free_c_string`.
724+
pub error: *const c_char,
725+
}
726+
727+
impl CompletionResult {
728+
fn success(candidates: *mut CompletionCandidateArray) -> Self {
729+
Self {
730+
candidates,
731+
error: ptr::null(),
732+
}
733+
}
734+
735+
fn error(message: &str) -> Self {
736+
Self {
737+
candidates: ptr::null_mut(),
738+
error: CString::new(message)
739+
.map(|s| s.into_raw().cast_const())
740+
.unwrap_or(ptr::null()),
741+
}
742+
}
743+
}
744+
745+
/// Runs completion for the given receiver and returns a structured result with candidates or an error message
719746
fn run_and_finalize_completion(
720747
graph: &mut Graph,
721748
receiver: CompletionReceiver,
722749
names_to_untrack: Vec<NameId>,
723-
) -> *mut CompletionCandidateArray {
724-
let Ok(candidates) = query::completion_candidates(graph, CompletionContext::new(receiver)) else {
725-
for name_id in names_to_untrack {
726-
graph.untrack_name(name_id);
750+
) -> CompletionResult {
751+
let candidates = match query::completion_candidates(graph, CompletionContext::new(receiver)) {
752+
Ok(candidates) => candidates,
753+
Err(e) => {
754+
for name_id in names_to_untrack {
755+
graph.untrack_name(name_id);
756+
}
757+
return CompletionResult::error(&e.to_string());
727758
}
728-
return ptr::null_mut();
729759
};
730760

731761
let entries: Vec<CCompletionCandidate> = candidates
732762
.into_iter()
733-
.filter_map(|candidate| {
734-
Some(match candidate {
735-
CompletionCandidate::Declaration(id) => {
736-
let decl = graph.declarations().get(&id)?;
737-
CCompletionCandidate {
738-
kind: CCompletionCandidateKind::Declaration,
739-
declaration: Box::into_raw(Box::new(CDeclaration::from_declaration(id, decl))),
740-
name: ptr::null(),
741-
documentation: ptr::null(),
742-
}
763+
.map(|candidate| match candidate {
764+
CompletionCandidate::Declaration(id) => {
765+
let decl = graph
766+
.declarations()
767+
.get(&id)
768+
.expect("completion candidate declaration must exist in graph");
769+
CCompletionCandidate {
770+
kind: CCompletionCandidateKind::Declaration,
771+
declaration: Box::into_raw(Box::new(CDeclaration::from_declaration(id, decl))),
772+
name: ptr::null(),
773+
documentation: ptr::null(),
743774
}
744-
CompletionCandidate::Keyword(kw) => CCompletionCandidate {
745-
kind: CCompletionCandidateKind::Keyword,
775+
}
776+
CompletionCandidate::Keyword(kw) => CCompletionCandidate {
777+
kind: CCompletionCandidateKind::Keyword,
778+
declaration: ptr::null(),
779+
name: CString::new(kw.name())
780+
.expect("keyword name must not contain NUL")
781+
.into_raw()
782+
.cast_const(),
783+
documentation: CString::new(kw.documentation())
784+
.expect("keyword documentation must not contain NUL")
785+
.into_raw()
786+
.cast_const(),
787+
},
788+
CompletionCandidate::KeywordArgument(str_id) => {
789+
let name_str = graph
790+
.strings()
791+
.get(&str_id)
792+
.expect("keyword argument string must exist in graph");
793+
CCompletionCandidate {
794+
kind: CCompletionCandidateKind::KeywordParameter,
746795
declaration: ptr::null(),
747-
name: CString::new(kw.name()).ok()?.into_raw().cast_const(),
748-
documentation: CString::new(kw.documentation()).ok()?.into_raw().cast_const(),
749-
},
750-
CompletionCandidate::KeywordArgument(str_id) => {
751-
let name_str = graph.strings().get(&str_id)?;
752-
CCompletionCandidate {
753-
kind: CCompletionCandidateKind::KeywordParameter,
754-
declaration: ptr::null(),
755-
name: CString::new(name_str.as_str()).ok()?.into_raw().cast_const(),
756-
documentation: ptr::null(),
757-
}
796+
name: CString::new(name_str.as_str())
797+
.expect("keyword argument name must not contain NUL")
798+
.into_raw()
799+
.cast_const(),
800+
documentation: ptr::null(),
758801
}
759-
})
802+
}
760803
})
761804
.collect();
762805

763806
for name_id in names_to_untrack {
764807
graph.untrack_name(name_id);
765808
}
766809

767-
CompletionCandidateArray::from_vec(entries)
810+
CompletionResult::success(CompletionCandidateArray::from_vec(entries))
768811
}
769812

770813
/// Returns expression completion candidates.
771-
/// The caller must free the result with `rdx_completion_candidates_free`.
814+
/// The caller must free candidates with `rdx_completion_candidates_free`
815+
/// and the error string (if non-null) with `free_c_string`.
772816
///
773817
/// # Safety
774818
///
@@ -779,19 +823,20 @@ pub unsafe extern "C" fn rdx_graph_complete_expression(
779823
pointer: GraphPointer,
780824
nesting: *const *const c_char,
781825
nesting_count: usize,
782-
) -> *mut CompletionCandidateArray {
826+
) -> CompletionResult {
783827
with_mut_graph(pointer, |graph| {
784828
let Ok((name_id, names_to_untrack)) = (unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
785829
else {
786-
return ptr::null_mut();
830+
return CompletionResult::success(ptr::null_mut());
787831
};
788832

789833
run_and_finalize_completion(graph, CompletionReceiver::Expression(name_id), names_to_untrack)
790834
})
791835
}
792836

793837
/// Returns namespace access completion candidates.
794-
/// The caller must free the result with `rdx_completion_candidates_free`.
838+
/// The caller must free candidates with `rdx_completion_candidates_free`
839+
/// and the error string (if non-null) with `free_c_string`.
795840
///
796841
/// # Safety
797842
///
@@ -801,9 +846,9 @@ pub unsafe extern "C" fn rdx_graph_complete_expression(
801846
pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
802847
pointer: GraphPointer,
803848
name: *const c_char,
804-
) -> *mut CompletionCandidateArray {
849+
) -> CompletionResult {
805850
let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
806-
return ptr::null_mut();
851+
return CompletionResult::success(ptr::null_mut());
807852
};
808853

809854
with_mut_graph(pointer, |graph| {
@@ -816,7 +861,8 @@ pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
816861
}
817862

818863
/// Returns method call completion candidates.
819-
/// The caller must free the result with `rdx_completion_candidates_free`.
864+
/// The caller must free candidates with `rdx_completion_candidates_free`
865+
/// and the error string (if non-null) with `free_c_string`.
820866
///
821867
/// # Safety
822868
///
@@ -826,9 +872,9 @@ pub unsafe extern "C" fn rdx_graph_complete_namespace_access(
826872
pub unsafe extern "C" fn rdx_graph_complete_method_call(
827873
pointer: GraphPointer,
828874
name: *const c_char,
829-
) -> *mut CompletionCandidateArray {
875+
) -> CompletionResult {
830876
let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
831-
return ptr::null_mut();
877+
return CompletionResult::success(ptr::null_mut());
832878
};
833879

834880
with_mut_graph(pointer, |graph| {
@@ -841,7 +887,8 @@ pub unsafe extern "C" fn rdx_graph_complete_method_call(
841887
}
842888

843889
/// Returns method argument completion candidates.
844-
/// The caller must free the result with `rdx_completion_candidates_free`.
890+
/// The caller must free candidates with `rdx_completion_candidates_free`
891+
/// and the error string (if non-null) with `free_c_string`.
845892
///
846893
/// # Safety
847894
///
@@ -854,16 +901,16 @@ pub unsafe extern "C" fn rdx_graph_complete_method_argument(
854901
name: *const c_char,
855902
nesting: *const *const c_char,
856903
nesting_count: usize,
857-
) -> *mut CompletionCandidateArray {
904+
) -> CompletionResult {
858905
let Ok(name_str) = (unsafe { utils::convert_char_ptr_to_string(name) }) else {
859-
return ptr::null_mut();
906+
return CompletionResult::success(ptr::null_mut());
860907
};
861908

862909
with_mut_graph(pointer, |graph| {
863910
let Ok((self_name_id, names_to_untrack)) =
864911
(unsafe { completion_nesting_name_id(graph, nesting, nesting_count) })
865912
else {
866-
return ptr::null_mut();
913+
return CompletionResult::success(ptr::null_mut());
867914
};
868915

869916
run_and_finalize_completion(

0 commit comments

Comments
 (0)