@@ -11,7 +11,8 @@ use masm_instructions::ToDescription;
1111use miden_assembly_syntax:: ast:: { Block , Instruction , Module , Op } ;
1212use miden_debug_types:: { DefaultSourceManager , SourceSpan , Span , Spanned } ;
1313use 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
1718use 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
335335fn 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