@@ -12,7 +12,7 @@ use crate::type_::error::{
1212 IncorrectArityContext , InvalidImportKind , MissingAnnotation , ModuleValueUsageContext , Named ,
1313 UnknownField , UnknownTypeHint , UnsafeRecordUpdateReason ,
1414} ;
15- use crate :: type_:: printer:: { Names , Printer } ;
15+ use crate :: type_:: printer:: { Names , Printer , TypeAnnotation , TypePathStep } ;
1616use crate :: type_:: { FieldAccessUsage , error:: PatternMatchKind } ;
1717use crate :: { ast:: BinOp , parse:: error:: ParseErrorType , type_:: Type } ;
1818use crate :: { bit_array, diagnostic:: Level , type_:: UnifyErrorSituation } ;
@@ -2143,6 +2143,9 @@ But function expects:
21432143 situation,
21442144 } => {
21452145 let mut printer = Printer :: new ( names) ;
2146+ let expected = collapse_links ( expected. clone ( ) ) ;
2147+ let given = collapse_links ( given. clone ( ) ) ;
2148+
21462149 let mut text = if let Some ( description) =
21472150 situation. as_ref ( ) . and_then ( |s| s. description ( ) )
21482151 {
@@ -2153,10 +2156,42 @@ But function expects:
21532156 } else {
21542157 "" . into ( )
21552158 } ;
2156- text. push_str ( "Expected type:\n \n " ) ;
2157- text. push_str ( & printer. print_type ( expected) ) ;
2158- text. push_str ( "\n \n Found type:\n \n " ) ;
2159- text. push_str ( & printer. print_type ( given) ) ;
2159+
2160+ let expected_display = printer. print_type ( & expected) ;
2161+ let ( given_display, annotations) =
2162+ printer. print_type_with_annotations ( & given) ;
2163+ let differences = collect_type_differences ( expected. clone ( ) , given. clone ( ) ) ;
2164+
2165+ // Only show smart diff suggestions if the type is complex enough
2166+ let highlight_lines = if should_show_smart_diff ( & differences) {
2167+ build_highlight_lines ( & differences, & annotations, & mut printer)
2168+ } else {
2169+ Vec :: new ( )
2170+ } ;
2171+
2172+ // Only show "Expected type:" if there are no smart diff suggestions
2173+ if highlight_lines. is_empty ( ) {
2174+ text. push_str ( "Expected type:\n \n " ) ;
2175+ text. push_str ( & expected_display) ;
2176+ text. push_str ( "\n \n " ) ;
2177+ }
2178+
2179+ text. push_str ( "Found type:\n \n " ) ;
2180+ text. push_str ( & given_display) ;
2181+
2182+ for highlight in highlight_lines {
2183+ text. push ( '\n' ) ;
2184+ text. push_str ( " " ) ;
2185+ for _ in 0 ..highlight. start {
2186+ text. push ( ' ' ) ;
2187+ }
2188+ let span_len = highlight. end . saturating_sub ( highlight. start ) . max ( 1 ) ;
2189+ for _ in 0 ..span_len {
2190+ text. push ( '^' ) ;
2191+ }
2192+ text. push_str ( " expected: " ) ;
2193+ text. push_str ( & highlight. expected ) ;
2194+ }
21602195
21612196 let ( main_message_location, main_message_text, extra_labels) =
21622197 match situation {
@@ -2170,9 +2205,11 @@ But function expects:
21702205 } ) => ( clause_location, None , vec ! [ ] ) ,
21712206 // In all other cases we just highlight the offending expression, optionally
21722207 // adding the wrapping hint if it makes sense.
2173- Some ( _) | None => {
2174- ( location, hint_wrap_value_in_result ( expected, given) , vec ! [ ] )
2175- }
2208+ Some ( _) | None => (
2209+ location,
2210+ hint_wrap_value_in_result ( & expected, & given) ,
2211+ vec ! [ ] ,
2212+ ) ,
21762213 } ;
21772214
21782215 Diagnostic {
@@ -4578,6 +4615,205 @@ fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> {
45784615 }
45794616}
45804617
4618+ #[ derive( Debug , Clone ) ]
4619+ struct TypeDifference {
4620+ path : Vec < TypePathStep > ,
4621+ expected : Arc < Type > ,
4622+ }
4623+
4624+ fn collect_type_differences ( expected : Arc < Type > , given : Arc < Type > ) -> Vec < TypeDifference > {
4625+ let mut differences = Vec :: new ( ) ;
4626+ let mut path = Vec :: new ( ) ;
4627+ collect_type_differences_inner ( expected, given, & mut path, & mut differences) ;
4628+ differences
4629+ }
4630+
4631+ fn collect_type_differences_inner (
4632+ expected : Arc < Type > ,
4633+ given : Arc < Type > ,
4634+ path : & mut Vec < TypePathStep > ,
4635+ differences : & mut Vec < TypeDifference > ,
4636+ ) {
4637+ let expected = collapse_links ( expected) ;
4638+ let given = collapse_links ( given) ;
4639+
4640+ if Arc :: ptr_eq ( & expected, & given) {
4641+ return ;
4642+ }
4643+
4644+ match ( & * expected, & * given) {
4645+ (
4646+ Type :: Named {
4647+ module : module_expected,
4648+ name : name_expected,
4649+ arguments : arguments_expected,
4650+ ..
4651+ } ,
4652+ Type :: Named {
4653+ module : module_given,
4654+ name : name_given,
4655+ arguments : arguments_given,
4656+ ..
4657+ } ,
4658+ ) if module_expected == module_given
4659+ && name_expected == name_given
4660+ && arguments_expected. len ( ) == arguments_given. len ( ) =>
4661+ {
4662+ for ( index, ( expected_argument, given_argument) ) in arguments_expected
4663+ . iter ( )
4664+ . zip ( arguments_given. iter ( ) )
4665+ . enumerate ( )
4666+ {
4667+ path. push ( TypePathStep :: NamedArgument ( index) ) ;
4668+ collect_type_differences_inner (
4669+ expected_argument. clone ( ) ,
4670+ given_argument. clone ( ) ,
4671+ path,
4672+ differences,
4673+ ) ;
4674+ let _ = path. pop ( ) ;
4675+ }
4676+ }
4677+ (
4678+ Type :: Fn {
4679+ arguments : arguments_expected,
4680+ return_ : return_expected,
4681+ } ,
4682+ Type :: Fn {
4683+ arguments : arguments_given,
4684+ return_ : return_given,
4685+ } ,
4686+ ) if arguments_expected. len ( ) == arguments_given. len ( ) => {
4687+ for ( index, ( expected_argument, given_argument) ) in arguments_expected
4688+ . iter ( )
4689+ . zip ( arguments_given. iter ( ) )
4690+ . enumerate ( )
4691+ {
4692+ path. push ( TypePathStep :: FnArgument ( index) ) ;
4693+ collect_type_differences_inner (
4694+ expected_argument. clone ( ) ,
4695+ given_argument. clone ( ) ,
4696+ path,
4697+ differences,
4698+ ) ;
4699+ let _ = path. pop ( ) ;
4700+ }
4701+ path. push ( TypePathStep :: FnReturn ) ;
4702+ collect_type_differences_inner (
4703+ return_expected. clone ( ) ,
4704+ return_given. clone ( ) ,
4705+ path,
4706+ differences,
4707+ ) ;
4708+ let _ = path. pop ( ) ;
4709+ }
4710+ (
4711+ Type :: Tuple {
4712+ elements : elements_expected,
4713+ ..
4714+ } ,
4715+ Type :: Tuple {
4716+ elements : elements_given,
4717+ ..
4718+ } ,
4719+ ) if elements_expected. len ( ) == elements_given. len ( ) => {
4720+ for ( index, ( expected_element, given_element) ) in elements_expected
4721+ . iter ( )
4722+ . zip ( elements_given. iter ( ) )
4723+ . enumerate ( )
4724+ {
4725+ path. push ( TypePathStep :: TupleElement ( index) ) ;
4726+ collect_type_differences_inner (
4727+ expected_element. clone ( ) ,
4728+ given_element. clone ( ) ,
4729+ path,
4730+ differences,
4731+ ) ;
4732+ let _ = path. pop ( ) ;
4733+ }
4734+ }
4735+ _ => differences. push ( TypeDifference {
4736+ path : path. clone ( ) ,
4737+ expected,
4738+ } ) ,
4739+ }
4740+ }
4741+
4742+ #[ derive( Debug ) ]
4743+ struct HighlightLine {
4744+ start : usize ,
4745+ end : usize ,
4746+ expected : EcoString ,
4747+ }
4748+
4749+ fn build_highlight_lines (
4750+ differences : & [ TypeDifference ] ,
4751+ annotations : & [ TypeAnnotation ] ,
4752+ printer : & mut Printer < ' _ > ,
4753+ ) -> Vec < HighlightLine > {
4754+ let mut highlights = Vec :: new ( ) ;
4755+
4756+ for difference in differences {
4757+ if difference. path . is_empty ( ) {
4758+ continue ;
4759+ }
4760+
4761+ if let Some ( annotation) = annotations
4762+ . iter ( )
4763+ . find ( |annotation| annotation. path == difference. path )
4764+ {
4765+ if annotation. range . start == annotation. range . end {
4766+ continue ;
4767+ }
4768+ let expected = printer. print_type ( & difference. expected ) ;
4769+ highlights. push ( HighlightLine {
4770+ start : annotation. range . start ,
4771+ end : annotation. range . end ,
4772+ expected,
4773+ } ) ;
4774+ }
4775+ }
4776+
4777+ highlights. sort_by_key ( |highlight| highlight. start ) ;
4778+ highlights
4779+ }
4780+
4781+ /// Determines if the type mismatch is "complex enough" to warrant showing smart diff suggestions.
4782+ ///
4783+ /// Smart diffs are helpful when:
4784+ /// 1. There are multiple type parameter mismatches (2+)
4785+ /// 2. The mismatch occurs at depth >= 2 (nested within nested types)
4786+ /// 3. The parent type has multiple parameters (2+) with at least one mismatch
4787+ fn should_show_smart_diff ( differences : & [ TypeDifference ] ) -> bool {
4788+ // Filter out empty path differences (top-level mismatches)
4789+ let nested_diffs: Vec < _ > = differences. iter ( ) . filter ( |d| !d. path . is_empty ( ) ) . collect ( ) ;
4790+
4791+ if nested_diffs. is_empty ( ) {
4792+ return false ;
4793+ }
4794+
4795+ // Count how many distinct mismatches we have
4796+ let num_mismatches = nested_diffs. len ( ) ;
4797+
4798+ // Check the maximum depth of mismatches
4799+ let max_depth = nested_diffs. iter ( ) . map ( |d| d. path . len ( ) ) . max ( ) . unwrap_or ( 0 ) ;
4800+
4801+ // Criterion 1: Multiple mismatches (2+) at any depth
4802+ if num_mismatches >= 2 {
4803+ return true ;
4804+ }
4805+
4806+ // Criterion 2: Deep nesting (depth >= 2)
4807+ // For example: Wrapper(List(Int)) has depth 2
4808+ if max_depth >= 2 {
4809+ return true ;
4810+ }
4811+
4812+ // If we get here, we have exactly 1 mismatch at depth 1
4813+ // This is like List(Int) vs List(String) - simple enough to not need smart diff
4814+ false
4815+ }
4816+
45814817fn hint_wrap_value_in_result ( expected : & Arc < Type > , given : & Arc < Type > ) -> Option < String > {
45824818 let expected = collapse_links ( expected. clone ( ) ) ;
45834819 let ( expected_ok_type, expected_error_type) = expected. result_types ( ) ?;
0 commit comments