Skip to content

Commit 4c87bd6

Browse files
committed
Refactored LiftingError
1 parent ac510e6 commit 4c87bd6

File tree

1 file changed

+150
-26
lines changed

1 file changed

+150
-26
lines changed

src/inlay_hints.rs

Lines changed: 150 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use masm_instructions::ToDescription;
1111
use miden_assembly_syntax::ast::{Block, Instruction, Module, Op};
1212
use miden_debug_types::{DefaultSourceManager, SourceSpan, Span, Spanned};
1313
use tower_lsp::lsp_types::{
14-
Diagnostic, DiagnosticSeverity, InlayHint, InlayHintKind, InlayHintLabel, Position, Range, Url,
14+
Diagnostic, DiagnosticSeverity, InlayHint, InlayHintKind, InlayHintLabel, NumberOrString,
15+
Position, Range, Url,
1516
};
1617

1718
use crate::diagnostics::{normalize_message, span_to_range, SOURCE_DECOMPILATION};
@@ -95,18 +96,17 @@ fn collect_decompilation_hints(
9596
const DEFAULT_INLAY_HINT_TABS: u32 = 2;
9697
let padding = DEFAULT_INLAY_HINT_TABS;
9798

98-
let mut proc_infos: Vec<(u32, String, bool)> = Vec::new();
99+
let mut proc_infos: Vec<(Range, String, bool)> = Vec::new();
99100
for proc in module.procedures() {
100101
let Some(range) = span_to_range(sources.as_ref(), proc.name().span()) else {
101102
continue;
102103
};
103-
let line = range.start.line;
104-
let in_range = line_in_range(line, visible_range);
104+
let in_range = line_in_range(range.start.line, visible_range);
105105
let proc_name = proc.name().as_str().to_string();
106-
proc_infos.push((line, proc_name, in_range));
106+
proc_infos.push((range, proc_name, in_range));
107107
}
108108

109-
proc_infos.sort_by_key(|(line, _, _)| *line);
109+
proc_infos.sort_by_key(|(range, _, _)| range.start.line);
110110
let mut hints = Vec::new();
111111
let mut diagnostics = Vec::new();
112112
if !unresolved_modules.is_empty() {
@@ -117,7 +117,8 @@ fn collect_decompilation_hints(
117117
));
118118
}
119119
for idx in 0..proc_infos.len() {
120-
let (line, proc_name, in_range) = proc_infos[idx].clone();
120+
let (proc_range, proc_name, in_range) = proc_infos[idx].clone();
121+
let line = proc_range.start.line;
121122

122123
if !in_range {
123124
continue;
@@ -133,14 +134,13 @@ fn collect_decompilation_hints(
133134
Ok(decompiled) => decompiled,
134135
Err(error) => {
135136
if !unresolved_modules.is_empty()
136-
&& matches!(
137-
error,
138-
DecompilationError::Lifting(LiftingError::UnknownCallTarget { .. })
139-
)
137+
&& should_suppress_with_unresolved_dependencies(&error)
140138
{
141139
continue;
142140
}
143-
if let Some(diag) = decompilation_error_diagnostic(sources.as_ref(), error) {
141+
if let Some(diag) =
142+
decompilation_error_diagnostic(sources.as_ref(), proc_range.clone(), error)
143+
{
144144
diagnostics.push(diag);
145145
}
146146
continue;
@@ -152,8 +152,8 @@ fn collect_decompilation_hints(
152152
let output = writer.finish();
153153
let mut lines: Vec<String> = output.lines().map(|line| line.to_string()).collect();
154154

155-
if let Some((next_line, _, _)) = proc_infos.get(idx + 1) {
156-
let allowed_lines = *next_line as i32 - line as i32 - 1;
155+
if let Some((next_proc_range, _, _)) = proc_infos.get(idx + 1) {
156+
let allowed_lines = next_proc_range.start.line as i32 - line as i32 - 1;
157157
if allowed_lines <= 0 {
158158
continue;
159159
}
@@ -334,34 +334,158 @@ fn line_in_range(line: u32, range: &Range) -> bool {
334334

335335
fn decompilation_error_diagnostic(
336336
sources: &DefaultSourceManager,
337+
fallback_range: Range,
337338
error: DecompilationError,
338339
) -> Option<Diagnostic> {
339-
let (span, message) = match error {
340-
DecompilationError::Lifting(err) => (lifting_error_span(&err), err.to_string()),
340+
let (range, kind, message) = match error {
341+
DecompilationError::Lifting(err) => {
342+
let message = err.to_string();
343+
let kind = classify_lifting_error(&message);
344+
let message = lift_diagnostic_message(kind, &message);
345+
let range = lifting_error_span(&err)
346+
.and_then(|span| span_to_range(sources, span))
347+
.unwrap_or(fallback_range);
348+
(range, kind, message)
349+
}
341350
DecompilationError::ProcedureNotFound(_) | DecompilationError::ModuleNotFound(_) => {
342351
return None;
343352
}
344353
};
345354

346-
let range = span_to_range(sources, span)
347-
.unwrap_or_else(|| Range::new(Position::new(0, 0), Position::new(0, 0)));
348-
349355
Some(Diagnostic {
350356
range,
351357
severity: Some(DiagnosticSeverity::ERROR),
352358
source: Some(SOURCE_DECOMPILATION.to_string()),
359+
code: Some(NumberOrString::String(kind.code().to_string())),
353360
message: normalize_message(&message),
354361
..Default::default()
355362
})
356363
}
357364

358-
fn lifting_error_span(error: &LiftingError) -> SourceSpan {
365+
fn should_suppress_with_unresolved_dependencies(error: &DecompilationError) -> bool {
366+
match error {
367+
DecompilationError::Lifting(err) => {
368+
classify_lifting_error(&err.to_string()).is_call_related()
369+
}
370+
DecompilationError::ProcedureNotFound(_) | DecompilationError::ModuleNotFound(_) => false,
371+
}
372+
}
373+
374+
#[derive(Clone, Copy)]
375+
enum LiftingDiagnosticKind {
376+
CallTargetResolution,
377+
MissingSignature,
378+
UnknownSignature,
379+
Other,
380+
}
381+
382+
impl LiftingDiagnosticKind {
383+
fn code(self) -> &'static str {
384+
match self {
385+
Self::CallTargetResolution => "call-target-resolution",
386+
Self::MissingSignature => "missing-signature",
387+
Self::UnknownSignature => "unknown-signature",
388+
Self::Other => "lifting-error",
389+
}
390+
}
391+
392+
fn is_call_related(self) -> bool {
393+
matches!(
394+
self,
395+
Self::CallTargetResolution | Self::MissingSignature | Self::UnknownSignature
396+
)
397+
}
398+
}
399+
400+
fn classify_lifting_error(message: &str) -> LiftingDiagnosticKind {
401+
let message = message.to_ascii_lowercase();
402+
403+
if message.contains("missing inferred signature") {
404+
return LiftingDiagnosticKind::MissingSignature;
405+
}
406+
if message.contains("unknown inferred signature") {
407+
return LiftingDiagnosticKind::UnknownSignature;
408+
}
409+
if message.contains("call target")
410+
&& (message.contains("resolve") || message.contains("unknown"))
411+
{
412+
return LiftingDiagnosticKind::CallTargetResolution;
413+
}
414+
415+
LiftingDiagnosticKind::Other
416+
}
417+
418+
fn lift_diagnostic_message(kind: LiftingDiagnosticKind, message: &str) -> String {
419+
match kind {
420+
LiftingDiagnosticKind::CallTargetResolution => {
421+
format!("{message}. Check library paths and unresolved imports used by this module")
422+
}
423+
LiftingDiagnosticKind::MissingSignature => {
424+
format!("{message}. The call resolved, but no inferred stack effect was found")
425+
}
426+
LiftingDiagnosticKind::UnknownSignature => {
427+
format!("{message}. The call resolved, but its inferred stack effect is unknown")
428+
}
429+
LiftingDiagnosticKind::Other => message.to_string(),
430+
}
431+
}
432+
433+
fn lifting_error_span(error: &LiftingError) -> Option<SourceSpan> {
359434
match error {
360-
LiftingError::UnsupportedInstruction { span, .. } => *span,
361-
LiftingError::UnknownCallTarget { span, .. } => *span,
362-
LiftingError::UnbalancedIf { span } => *span,
363-
LiftingError::NonNeutralWhile { span } => *span,
364-
LiftingError::IncompatibleIfMerge { span } => *span,
365-
LiftingError::UnsupportedRepeatPattern { span, .. } => *span,
435+
LiftingError::UnsupportedInstruction { span, .. } => Some(*span),
436+
LiftingError::UnbalancedIf { span } => Some(*span),
437+
LiftingError::NonNeutralWhile { span } => Some(*span),
438+
LiftingError::IncompatibleIfMerge { span } => Some(*span),
439+
LiftingError::UnsupportedRepeatPattern { span, .. } => Some(*span),
440+
_ => None,
441+
}
442+
}
443+
444+
#[cfg(test)]
445+
mod tests {
446+
use super::{classify_lifting_error, lift_diagnostic_message, LiftingDiagnosticKind};
447+
448+
#[test]
449+
fn classify_old_unknown_call_target_message() {
450+
let message = "unknown call target `foo::bar` found";
451+
assert!(matches!(
452+
classify_lifting_error(message),
453+
LiftingDiagnosticKind::CallTargetResolution
454+
));
455+
}
456+
457+
#[test]
458+
fn classify_new_unresolved_call_target_message() {
459+
let message = "failed to resolve call target `foo::bar`: module not loaded";
460+
assert!(matches!(
461+
classify_lifting_error(message),
462+
LiftingDiagnosticKind::CallTargetResolution
463+
));
464+
}
465+
466+
#[test]
467+
fn classify_new_missing_signature_message() {
468+
let message = "missing inferred signature for call target `foo::bar`";
469+
assert!(matches!(
470+
classify_lifting_error(message),
471+
LiftingDiagnosticKind::MissingSignature
472+
));
473+
}
474+
475+
#[test]
476+
fn classify_new_unknown_signature_message() {
477+
let message = "call target `foo::bar` has unknown inferred signature";
478+
assert!(matches!(
479+
classify_lifting_error(message),
480+
LiftingDiagnosticKind::UnknownSignature
481+
));
482+
}
483+
484+
#[test]
485+
fn call_target_messages_include_actionable_hint() {
486+
let message = "failed to resolve call target `foo::bar`";
487+
let rendered =
488+
lift_diagnostic_message(LiftingDiagnosticKind::CallTargetResolution, message);
489+
assert!(rendered.contains("Check library paths and unresolved imports"));
366490
}
367491
}

0 commit comments

Comments
 (0)