Improve TypesDoNotUnify message for record types · Issue #4413 · purescript/purescript · GitHub
Skip to content

Improve TypesDoNotUnify message for record types #4413

Open

Description

@rpolley

Summary

We should call out what difference in the records caused the match to fail in addition to the current output.

Motivation

When making heavy use of row polymorphism in a large codebase one often ends up with functions on record types that expect some number of labels with a generic row tail, which turns the error messages for two row types not matching into a bit of a spot the differences game, and this gets worse and worse the more stuff you add to these, or if you start nesting records.

Proposal

Call out what didn't match in the error message, i.e. something like 'expected the label "label :: Type"', or "type cannot have the label "label :: Type"'.

Examples

Type Stuff s =
   { foo: Foo
  , bar: Bar
  , baz: Baz
  | s
  }

Let's say you have a function with the following type

doThingWithStuff :: forall s. Stuff s -> SomeOtherType

Suppose also that you have a type

type Stuff2 s =
{ foo: Foo
, bar: Bar
, qux: Qux
, quaz: Quaz
| s
}

but which is missing the bar :: Bar label, and a value stuff2 :: Stuff2 (). Now say you pass stuff2 to doThingWithStuff. The error message isn't directly helpful at finding the actual problem, which is that Stuff2 is missing the label bar :: Bar. The error message should mention that the types failed to unify because the first has a label bar :: Bar and the second doesn't.

Now let's say you also have a function mkStuff :: StuffPrecursor -> Stuff () and you put in an extra label. It would be helpful if the compiler told you that your record has the extra label directly instead of giving a generic row TypesDoNotUnify message.

Activity

  1. MonoidMusician commented on Nov 2, 2022

    @MonoidMusician
    Contributor

    There are already errors for this, but it looks like they are only used during subsumption, not during unification (see the MissingRecordField and ExtraRecordField tests):

    The error message would need some tweaking, since it isn't saying the right thing for unification, but I think it is a good idea to do what we can to improve these errors.

    The difference between subsumption and unification is that the compiler is allowed to apply typeclass instances during subsumption, but not during unification. The compiler would use unification instead of subsumption if you have even something as simple as the Identity type constructor in the way:

    john = Identity { first: "John", last: "Smith" }
    
    isOver50 (Identity p) = p.age
    
    result = isOver50 john

    This is because the compiler will not do subsumption through type constructors, it resorts to unification then (* except function types? maybe? I don't actually know). (The reason it won't do that is that it would require taking apart and putting back together the datatype to apply the instances, and it doesn't even keep track of which parameters are covariant or contravariant.)

    The tricky part is that unification mismatches can apply in e.g. contravariant contexts, so you don't actually know which labels was missing and which labels were extraneous, you can only see that there was some mismatch. (See also the infamous #3399.)

  2. rpolley commented on Nov 2, 2022

    @rpolley
    Author

    Honestly even just calling out what's actually different between the two types would probably be helpful.

  3. FredTheDino commented on Nov 15, 2022

    @FredTheDino
    Contributor

    I also hate when this happens (#same). But I'm also scared of the type-checker. I'm very interested in looking at this.

    But I'm not sure we have to go digging in the type-checker. When we write out the errors, we know what is in each record (at least parts of it). We could do a super simple diff on those records and simply display that information. It could possibly remove some noise and make it easier to see the actual difference.

    Here's a toy example:

    -- As we can see, Stuff > Thing
    type Stuff s = { foo :: Int | s }
    
    doStuff :: forall s. Stuff s -> Int
    doStuff { foo, bar } = bar
    

    E.g.

      Could not match type
    
        ( foo :: Int
        ...
        | s0
        )
    
      with type
    
        ( bar :: t2
        , foo :: t3
        ...
        | t4
        )
    

    Here we could make the compiler do parts of the diffing for us in the error message. Then we could present it as something like:

       Found extra labels:
          ( "bar" :: t2
          ...
          )
    

    This would solve most of the problems I've experienced. I also truly hate "spot the differences games".

    This won't solve all of the problem. But I think it's quite an easy fix that gets us some of the way there. Because the information is there, it's just really poorly presented.

    Another alternative is color coding the labels that don't exist in the other output as being red or something. But I kinda like the idea of removing some of the text from the type errors, since they are way to big as they are.

  4. JordanMartinez commented on Jul 22, 2023

    @JordanMartinez
    Contributor

    Looking at #4421, a few thoughts come to mind.

    First, this issue is complicated because making a change that makes an error message more understandable for one user may make a different error message less understandable for a different user. I think this is the main issue with that PR.

    Second, when developers hit compiler errors, I think there are two generally kinds of workflows they take:

    1. They understand what the underlying issue is and know how to solve the compiler error:
      1. build the code
      2. read the error message
      3. fix the error and know that it works
      4. write more code / fix next error that now appears
    2. They do not know what the underlying issue is, and so take a "spray-and-pray" approach to solving it:
      1. build the code
      2. read the error message and try to make sense of it
      3. try something and hope it works; maybe ask someone for help
      4. repeat

    Perhaps a better way forward is to allow the user more control when they hit Workflow 2 above?

    If a user could use some kind of mechanism in these ad-hoc cases, they could build their code multiple times with different row-error-reporting strategies, each of which further informs them about the issue. This would deal with the first problem raised above about harming user A to benefit user B: such "harm" to user A only applies if they use the mechanism intended for user B.

    If such a mechanism was supported, then the approach currently taken in #4421 (i.e. the fromRowList approach) could be opted-into by the user. However, Ryan's second suggestion (i.e. the substituteType approach) could also be opted-into by the user. I don't know if those are the only two cases, but it would allow benefits to user B without harming user A and thus make progress on the issue.

    As for the mechanism, we could use a CLI flag, but I think that's undesirable. Keeping the CLI surface small is a net win IMO. The mechanism I'm considering is using an annotation (i.e. #4482). It's something I can quickly add to my code for debugging purposes and then immediately remove thereafter. Moreover, I can imagine a scenario where I annotate multiple pieces of my code and the compiler uses that information to inform its error-reporting. Perhaps I know that some type alias, some type class, and a particular usage of the two is what is involved in the problem. Annotating all of them would cause the compiler to produce "better errors" for that particular ad-hoc case. And since these are meant for debugging purposes, any such usage of annotations would produce a compiler warning to encourage the user to remove such annotations once they have finished debugging the problem.

    In this way, these error-reporting annotations are like temporary in-source-code error-reporting CLI flags/arguments that don't increase the CLI surface.

    Thoughts?

  5. rhendric commented on Jul 23, 2023

    @rhendric
    Member

    I think #4421 is pretty close to mergable; it isn't stuck on a design consideration, just on someone with enough on-the-ground experience with this particular frustration to be able to sign off on what is and isn't an improvement who is willing to answer the remaining questions and take the time to get it over the finish line.

    That said, annotations to control the manner in which error messages are presented are an interesting idea and, if another part of this design space ends up being contentious and if #4482 actually happens, it would be a good possibility to consider.

  6. JordanMartinez commented on Oct 28, 2023

    @JordanMartinez
    Contributor

    Continuing from the discussion from #4421 (comment), where the focus changed to improving row errors when Row.Union constraints are used, Ryan indicated how we could move forward via:

    From a getting-things-done perspective, whichever one of us gets cycles first should make a test case that mimics the react-basic API and start sketching out ways to make that error more digestible.

    Using react-basic-hooks-dom's div component's API as an example, I added the following failing test locally. I updated various type signatures to reduce the number of imports needed. Then, I ran it on both master and #4421. Both versions produce the exact same output.

    UnionRowErrors.purs
    -- @shouldFailWith TypesDoNotUnify
    module Main where
    
    import Prelude
    
    import Unsafe.Coerce (unsafeCoerce)
    import Prim.Row as Row
    
    test :: JSX
    test =
      div
        { classNameBad: "foo" }
    
    div
      :: forall attrs attrs_
       . Row.Union attrs attrs_ Props_div
      => Record attrs
      -> JSX
    div = unsafeCoerce
    
    foreign import data JSX :: Type
    
    foreign import data SyntheticEvent :: Type
    
    type EventHandler = SyntheticEvent -> Unit
    
    type Props_div =
      ( _aria :: Array String
      , _data :: Array String
      , about :: String
      , acceptCharset :: String
      , accessKey :: String
      , allowFullScreen :: Boolean
      , allowTransparency :: Boolean
      , autoFocus :: Boolean
      , autoPlay :: Boolean
      , capture :: Boolean
      , cellPadding :: String
      , cellSpacing :: String
      , charSet :: String
      , children :: Array JSX
      , classID :: String
      , className :: String -- we want this field name, but spelled it wrong...Zz
      , colSpan :: Int
      , contentEditable :: Boolean
      , contextMenu :: String
      , crossOrigin :: String
      , dangerouslySetInnerHTML :: { __html :: String }
      , datatype :: String
      , dateTime :: String
      , dir :: String
      , draggable :: Boolean
      , encType :: String
      , formAction :: String
      , formEncType :: String
      , formMethod :: String
      , formNoValidate :: Boolean
      , formTarget :: String
      , frameBorder :: String
      , hidden :: Boolean
      , hrefLang :: String
      , htmlFor :: String
      , httpEquiv :: String
      , icon :: String
      , id :: String
      , inlist :: String
      , inputMode :: String
      , is :: String
      , itemID :: String
      , itemProp :: String
      , itemRef :: String
      , itemScope :: Boolean
      , itemType :: String
      , key :: String
      , keyParams :: String
      , keyType :: String
      , lang :: String
      , marginHeight :: String
      , marginWidth :: String
      , maxLength :: Int
      , mediaGroup :: String
      , minLength :: Int
      , noValidate :: Boolean
      , onAnimationEnd :: EventHandler
      , onAnimationIteration :: EventHandler
      , onAnimationStart :: EventHandler
      , onBlur :: EventHandler
      , onClick :: EventHandler
      , onCompositionEnd :: EventHandler
      , onCompositionStart :: EventHandler
      , onCompositionUpdate :: EventHandler
      , onContextMenu :: EventHandler
      , onCopy :: EventHandler
      , onCut :: EventHandler
      , onDoubleClick :: EventHandler
      , onDrag :: EventHandler
      , onDragEnd :: EventHandler
      , onDragEnter :: EventHandler
      , onDragExit :: EventHandler
      , onDragLeave :: EventHandler
      , onDragOver :: EventHandler
      , onDragStart :: EventHandler
      , onDrop :: EventHandler
      , onFocus :: EventHandler
      , onGotPointerCapture :: EventHandler
      , onInvalid :: EventHandler
      , onKeyDown :: EventHandler
      , onKeyPress :: EventHandler
      , onKeyUp :: EventHandler
      , onLostPointerCapture :: EventHandler
      , onMouseDown :: EventHandler
      , onMouseEnter :: EventHandler
      , onMouseLeave :: EventHandler
      , onMouseMove :: EventHandler
      , onMouseOut :: EventHandler
      , onMouseOver :: EventHandler
      , onMouseUp :: EventHandler
      , onPaste :: EventHandler
      , onPointerCancel :: EventHandler
      , onPointerDown :: EventHandler
      , onPointerEnter :: EventHandler
      , onPointerLeave :: EventHandler
      , onPointerMove :: EventHandler
      , onPointerOut :: EventHandler
      , onPointerOver :: EventHandler
      , onPointerUp :: EventHandler
      , onSelect :: EventHandler
      , onSubmit :: EventHandler
      , onTouchCancel :: EventHandler
      , onTouchEnd :: EventHandler
      , onTouchMove :: EventHandler
      , onTouchStart :: EventHandler
      , onTransitionEnd :: EventHandler
      , onWheel :: EventHandler
      , prefix :: String
      , property :: String
      , radioGroup :: String
      , readOnly :: Boolean
      , ref :: Array Int
      , resource :: String
      , role :: String
      , rowSpan :: Int
      , scoped :: Boolean
      , seamless :: Boolean
      , security :: String
      , slot :: String
      , spellCheck :: Boolean
      , srcDoc :: JSX
      , srcLang :: String
      , srcSet :: String
      , style :: String
      , suppressContentEditableWarning :: Boolean
      , tabIndex :: Int
      , title :: String
      , typeof :: String
      , unselectable :: Boolean
      , useMap :: String
      , vocab :: String
      , wmode :: String
      )
    Error message output
      1) compiler, Failing examples, 'UnionRowErrors.purs' should fail to compile
           Test output differed from 'tests/purs/failing/UnionRowErrors.out'; got:
           Error found:
           in module Main
           at tests/purs/failing/UnionRowErrors.purs:11:3 - 12:28 (line 11, column 3 - line 12, column 28)
           
             Could not match type
                                       
               ( classNameBad :: String
               ...                     
               | t1                    
               )                       
                                       
             with type
                                                               
               ( _aria :: Array String                         
               , _data :: Array String                         
               , about :: String                               
               , acceptCharset :: String                       
               , accessKey :: String                           
               , allowFullScreen :: Boolean                    
               , allowTransparency :: Boolean                  
               , autoFocus :: Boolean                          
               , autoPlay :: Boolean                           
               , capture :: Boolean                            
               , cellPadding :: String                         
               , cellSpacing :: String                         
               , charSet :: String                             
               , children :: Array JSX                         
               , classID :: String                             
               , className :: String                           
               , colSpan :: Int                                
               , contentEditable :: Boolean                    
               , contextMenu :: String                         
               , crossOrigin :: String                         
               , dangerouslySetInnerHTML :: { __html :: String 
                                            }                  
               , datatype :: String                            
               , dateTime :: String                            
               , dir :: String                                 
               , draggable :: Boolean                          
               , encType :: String                             
               , formAction :: String                          
               , formEncType :: String                         
               , formMethod :: String                          
               , formNoValidate :: Boolean                     
               , formTarget :: String                          
               , frameBorder :: String                         
               , hidden :: Boolean                             
               , hrefLang :: String                            
               , htmlFor :: String                             
               , httpEquiv :: String                           
               , icon :: String                                
               , id :: String                                  
               , inlist :: String                              
               , inputMode :: String                           
               , is :: String                                  
               , itemID :: String                              
               , itemProp :: String                            
               , itemRef :: String                             
               , itemScope :: Boolean                          
               , itemType :: String                            
               , key :: String                                 
               , keyParams :: String                           
               , keyType :: String                             
               , lang :: String                                
               , marginHeight :: String                        
               , marginWidth :: String                         
               , maxLength :: Int                              
               , mediaGroup :: String                          
               , minLength :: Int                              
               , noValidate :: Boolean                         
               , onAnimationEnd :: SyntheticEvent -> Unit      
               , onAnimationIteration :: SyntheticEvent -> Unit
               , onAnimationStart :: SyntheticEvent -> Unit    
               , onBlur :: SyntheticEvent -> Unit              
               , onClick :: SyntheticEvent -> Unit             
               , onCompositionEnd :: SyntheticEvent -> Unit    
               , onCompositionStart :: SyntheticEvent -> Unit  
               , onCompositionUpdate :: SyntheticEvent -> Unit 
               , onContextMenu :: SyntheticEvent -> Unit       
               , onCopy :: SyntheticEvent -> Unit              
               , onCut :: SyntheticEvent -> Unit               
               , onDoubleClick :: SyntheticEvent -> Unit       
               , onDrag :: SyntheticEvent -> Unit              
               , onDragEnd :: SyntheticEvent -> Unit           
               , onDragEnter :: SyntheticEvent -> Unit         
               , onDragExit :: SyntheticEvent -> Unit          
               , onDragLeave :: SyntheticEvent -> Unit         
               , onDragOver :: SyntheticEvent -> Unit          
               , onDragStart :: SyntheticEvent -> Unit         
               , onDrop :: SyntheticEvent -> Unit              
               , onFocus :: SyntheticEvent -> Unit             
               , onGotPointerCapture :: SyntheticEvent -> Unit 
               , onInvalid :: SyntheticEvent -> Unit           
               , onKeyDown :: SyntheticEvent -> Unit           
               , onKeyPress :: SyntheticEvent -> Unit          
               , onKeyUp :: SyntheticEvent -> Unit             
               , onLostPointerCapture :: SyntheticEvent -> Unit
               , onMouseDown :: SyntheticEvent -> Unit         
               , onMouseEnter :: SyntheticEvent -> Unit        
               , onMouseLeave :: SyntheticEvent -> Unit        
               , onMouseMove :: SyntheticEvent -> Unit         
               , onMouseOut :: SyntheticEvent -> Unit          
               , onMouseOver :: SyntheticEvent -> Unit         
               , onMouseUp :: SyntheticEvent -> Unit           
               , onPaste :: SyntheticEvent -> Unit             
               , onPointerCancel :: SyntheticEvent -> Unit     
               , onPointerDown :: SyntheticEvent -> Unit       
               , onPointerEnter :: SyntheticEvent -> Unit      
               , onPointerLeave :: SyntheticEvent -> Unit      
               , onPointerMove :: SyntheticEvent -> Unit       
               , onPointerOut :: SyntheticEvent -> Unit        
               , onPointerOver :: SyntheticEvent -> Unit       
               , onPointerUp :: SyntheticEvent -> Unit         
               , onSelect :: SyntheticEvent -> Unit            
               , onSubmit :: SyntheticEvent -> Unit            
               , onTouchCancel :: SyntheticEvent -> Unit       
               , onTouchEnd :: SyntheticEvent -> Unit          
               , onTouchMove :: SyntheticEvent -> Unit         
               , onTouchStart :: SyntheticEvent -> Unit        
               , onTransitionEnd :: SyntheticEvent -> Unit     
               , onWheel :: SyntheticEvent -> Unit             
               , prefix :: String                              
               , property :: String                            
               , radioGroup :: String                          
               , readOnly :: Boolean                           
               , ref :: Array Int                              
               , resource :: String                            
               , role :: String                                
               , rowSpan :: Int                                
               , scoped :: Boolean                             
               , seamless :: Boolean                           
               , security :: String                            
               , slot :: String                                
               , spellCheck :: Boolean                         
               , srcDoc :: JSX                                 
               , srcLang :: String                             
               , srcSet :: String                              
               , style :: String                               
               , suppressContentEditableWarning :: Boolean     
               , tabIndex :: Int                               
               , title :: String                               
               , typeof :: String                              
               , unselectable :: Boolean                       
               , useMap :: String                              
               , vocab :: String                               
               , wmode :: String                               
               ...                                             
               )                                               
                                                               
           
           while solving type class constraint
                                                                            
             Prim.Row.Union ( classNameBad :: String                        
                            )                                               
                            t1                                              
                            ( _aria :: Array String                         
                            , _data :: Array String                         
                            , about :: String                               
                            , acceptCharset :: String                       
                            , accessKey :: String                           
                            , allowFullScreen :: Boolean                    
                            , allowTransparency :: Boolean                  
                            , autoFocus :: Boolean                          
                            , autoPlay :: Boolean                           
                            , capture :: Boolean                            
                            , cellPadding :: String                         
                            , cellSpacing :: String                         
                            , charSet :: String                             
                            , children :: Array JSX                         
                            , classID :: String                             
                            , className :: String                           
                            , colSpan :: Int                                
                            , contentEditable :: Boolean                    
                            , contextMenu :: String                         
                            , crossOrigin :: String                         
                            , dangerouslySetInnerHTML :: { __html :: String 
                                                         }                  
                            , datatype :: String                            
                            , dateTime :: String                            
                            , dir :: String                                 
                            , draggable :: Boolean                          
                            , encType :: String                             
                            , formAction :: String                          
                            , formEncType :: String                         
                            , formMethod :: String                          
                            , formNoValidate :: Boolean                     
                            , formTarget :: String                          
                            , frameBorder :: String                         
                            , hidden :: Boolean                             
                            , hrefLang :: String                            
                            , htmlFor :: String                             
                            , httpEquiv :: String                           
                            , icon :: String                                
                            , id :: String                                  
                            , inlist :: String                              
                            , inputMode :: String                           
                            , is :: String                                  
                            , itemID :: String                              
                            , itemProp :: String                            
                            , itemRef :: String                             
                            , itemScope :: Boolean                          
                            , itemType :: String                            
                            , key :: String                                 
                            , keyParams :: String                           
                            , keyType :: String                             
                            , lang :: String                                
                            , marginHeight :: String                        
                            , marginWidth :: String                         
                            , maxLength :: Int                              
                            , mediaGroup :: String                          
                            , minLength :: Int                              
                            , noValidate :: Boolean                         
                            , onAnimationEnd :: SyntheticEvent -> Unit      
                            , onAnimationIteration :: SyntheticEvent -> Unit
                            , onAnimationStart :: SyntheticEvent -> Unit    
                            , onBlur :: SyntheticEvent -> Unit              
                            , onClick :: SyntheticEvent -> Unit             
                            , onCompositionEnd :: SyntheticEvent -> Unit    
                            , onCompositionStart :: SyntheticEvent -> Unit  
                            , onCompositionUpdate :: SyntheticEvent -> Unit 
                            , onContextMenu :: SyntheticEvent -> Unit       
                            , onCopy :: SyntheticEvent -> Unit              
                            , onCut :: SyntheticEvent -> Unit               
                            , onDoubleClick :: SyntheticEvent -> Unit       
                            , onDrag :: SyntheticEvent -> Unit              
                            , onDragEnd :: SyntheticEvent -> Unit           
                            , onDragEnter :: SyntheticEvent -> Unit         
                            , onDragExit :: SyntheticEvent -> Unit          
                            , onDragLeave :: SyntheticEvent -> Unit         
                            , onDragOver :: SyntheticEvent -> Unit          
                            , onDragStart :: SyntheticEvent -> Unit         
                            , onDrop :: SyntheticEvent -> Unit              
                            , onFocus :: SyntheticEvent -> Unit             
                            , onGotPointerCapture :: SyntheticEvent -> Unit 
                            , onInvalid :: SyntheticEvent -> Unit           
                            , onKeyDown :: SyntheticEvent -> Unit           
                            , onKeyPress :: SyntheticEvent -> Unit          
                            , onKeyUp :: SyntheticEvent -> Unit             
                            , onLostPointerCapture :: SyntheticEvent -> Unit
                            , onMouseDown :: SyntheticEvent -> Unit         
                            , onMouseEnter :: SyntheticEvent -> Unit        
                            , onMouseLeave :: SyntheticEvent -> Unit        
                            , onMouseMove :: SyntheticEvent -> Unit         
                            , onMouseOut :: SyntheticEvent -> Unit          
                            , onMouseOver :: SyntheticEvent -> Unit         
                            , onMouseUp :: SyntheticEvent -> Unit           
                            , onPaste :: SyntheticEvent -> Unit             
                            , onPointerCancel :: SyntheticEvent -> Unit     
                            , onPointerDown :: SyntheticEvent -> Unit       
                            , onPointerEnter :: SyntheticEvent -> Unit      
                            , onPointerLeave :: SyntheticEvent -> Unit      
                            , onPointerMove :: SyntheticEvent -> Unit       
                            , onPointerOut :: SyntheticEvent -> Unit        
                            , onPointerOver :: SyntheticEvent -> Unit       
                            , onPointerUp :: SyntheticEvent -> Unit         
                            , onSelect :: SyntheticEvent -> Unit            
                            , onSubmit :: SyntheticEvent -> Unit            
                            , onTouchCancel :: SyntheticEvent -> Unit       
                            , onTouchEnd :: SyntheticEvent -> Unit          
                            , onTouchMove :: SyntheticEvent -> Unit         
                            , onTouchStart :: SyntheticEvent -> Unit        
                            , onTransitionEnd :: SyntheticEvent -> Unit     
                            , onWheel :: SyntheticEvent -> Unit             
                            , prefix :: String                              
                            , property :: String                            
                            , radioGroup :: String                          
                            , readOnly :: Boolean                           
                            , ref :: Array Int                              
                            , resource :: String                            
                            , role :: String                                
                            , rowSpan :: Int                                
                            , scoped :: Boolean                             
                            , seamless :: Boolean                           
                            , security :: String                            
                            , slot :: String                                
                            , spellCheck :: Boolean                         
                            , srcDoc :: JSX                                 
                            , srcLang :: String                             
                            , srcSet :: String                              
                            , style :: String                               
                            , suppressContentEditableWarning :: Boolean     
                            , tabIndex :: Int                               
                            , title :: String                               
                            , typeof :: String                              
                            , unselectable :: Boolean                       
                            , useMap :: String                              
                            , vocab :: String                               
                            , wmode :: String                               
                            )                                               
                                                                            
           while applying a function div
             of type Union @Type t0 t1                                 
                       ( _aria :: Array String                         
                       , _data :: Array String                         
                       , about :: String                               
                       , acceptCharset :: String                       
                       , accessKey :: String                           
                       , allowFullScreen :: Boolean                    
                       , allowTransparency :: Boolean                  
                       , autoFocus :: Boolean                          
                       , autoPlay :: Boolean                           
                       , capture :: Boolean                            
                       , cellPadding :: String                         
                       , cellSpacing :: String                         
                       , charSet :: String                             
                       , children :: Array JSX                         
                       , classID :: String                             
                       , className :: String                           
                       , colSpan :: Int                                
                       , contentEditable :: Boolean                    
                       , contextMenu :: String                         
                       , crossOrigin :: String                         
                       , dangerouslySetInnerHTML :: { __html :: String 
                                                    }                  
                       , datatype :: String                            
                       , dateTime :: String                            
                       , dir :: String                                 
                       , draggable :: Boolean                          
                       , encType :: String                             
                       , formAction :: String                          
                       , formEncType :: String                         
                       , formMethod :: String                          
                       , formNoValidate :: Boolean                     
                       , formTarget :: String                          
                       , frameBorder :: String                         
                       , hidden :: Boolean                             
                       , hrefLang :: String                            
                       , htmlFor :: String                             
                       , httpEquiv :: String                           
                       , icon :: String                                
                       , id :: String                                  
                       , inlist :: String                              
                       , inputMode :: String                           
                       , is :: String                                  
                       , itemID :: String                              
                       , itemProp :: String                            
                       , itemRef :: String                             
                       , itemScope :: Boolean                          
                       , itemType :: String                            
                       , key :: String                                 
                       , keyParams :: String                           
                       , keyType :: String                             
                       , lang :: String                                
                       , marginHeight :: String                        
                       , marginWidth :: String                         
                       , maxLength :: Int                              
                       , mediaGroup :: String                          
                       , minLength :: Int                              
                       , noValidate :: Boolean                         
                       , onAnimationEnd :: SyntheticEvent -> Unit      
                       , onAnimationIteration :: SyntheticEvent -> Unit
                       , onAnimationStart :: SyntheticEvent -> Unit    
                       , onBlur :: SyntheticEvent -> Unit              
                       , onClick :: SyntheticEvent -> Unit             
                       , onCompositionEnd :: SyntheticEvent -> Unit    
                       , onCompositionStart :: SyntheticEvent -> Unit  
                       , onCompositionUpdate :: SyntheticEvent -> Unit 
                       , onContextMenu :: SyntheticEvent -> Unit       
                       , onCopy :: SyntheticEvent -> Unit              
                       , onCut :: SyntheticEvent -> Unit               
                       , onDoubleClick :: SyntheticEvent -> Unit       
                       , onDrag :: SyntheticEvent -> Unit              
                       , onDragEnd :: SyntheticEvent -> Unit           
                       , onDragEnter :: SyntheticEvent -> Unit         
                       , onDragExit :: SyntheticEvent -> Unit          
                       , onDragLeave :: SyntheticEvent -> Unit         
                       , onDragOver :: SyntheticEvent -> Unit          
                       , onDragStart :: SyntheticEvent -> Unit         
                       , onDrop :: SyntheticEvent -> Unit              
                       , onFocus :: SyntheticEvent -> Unit             
                       , onGotPointerCapture :: SyntheticEvent -> Unit 
                       , onInvalid :: SyntheticEvent -> Unit           
                       , onKeyDown :: SyntheticEvent -> Unit           
                       , onKeyPress :: SyntheticEvent -> Unit          
                       , onKeyUp :: SyntheticEvent -> Unit             
                       , onLostPointerCapture :: SyntheticEvent -> Unit
                       , onMouseDown :: SyntheticEvent -> Unit         
                       , onMouseEnter :: SyntheticEvent -> Unit        
                       , onMouseLeave :: SyntheticEvent -> Unit        
                       , onMouseMove :: SyntheticEvent -> Unit         
                       , onMouseOut :: SyntheticEvent -> Unit          
                       , onMouseOver :: SyntheticEvent -> Unit         
                       , onMouseUp :: SyntheticEvent -> Unit           
                       , onPaste :: SyntheticEvent -> Unit             
                       , onPointerCancel :: SyntheticEvent -> Unit     
                       , onPointerDown :: SyntheticEvent -> Unit       
                       , onPointerEnter :: SyntheticEvent -> Unit      
                       , onPointerLeave :: SyntheticEvent -> Unit      
                       , onPointerMove :: SyntheticEvent -> Unit       
                       , onPointerOut :: SyntheticEvent -> Unit        
                       , onPointerOver :: SyntheticEvent -> Unit       
                       , onPointerUp :: SyntheticEvent -> Unit         
                       , onSelect :: SyntheticEvent -> Unit            
                       , onSubmit :: SyntheticEvent -> Unit            
                       , onTouchCancel :: SyntheticEvent -> Unit       
                       , onTouchEnd :: SyntheticEvent -> Unit          
                       , onTouchMove :: SyntheticEvent -> Unit         
                       , onTouchStart :: SyntheticEvent -> Unit        
                       , onTransitionEnd :: SyntheticEvent -> Unit     
                       , onWheel :: SyntheticEvent -> Unit             
                       , prefix :: String                              
                       , property :: String                            
                       , radioGroup :: String                          
                       , readOnly :: Boolean                           
                       , ref :: Array Int                              
                       , resource :: String                            
                       , role :: String                                
                       , rowSpan :: Int                                
                       , scoped :: Boolean                             
                       , seamless :: Boolean                           
                       , security :: String                            
                       , slot :: String                                
                       , spellCheck :: Boolean                         
                       , srcDoc :: JSX                                 
                       , srcLang :: String                             
                       , srcSet :: String                              
                       , style :: String                               
                       , suppressContentEditableWarning :: Boolean     
                       , tabIndex :: Int                               
                       , title :: String                               
                       , typeof :: String                              
                       , unselectable :: Boolean                       
                       , useMap :: String                              
                       , vocab :: String                               
                       , wmode :: String                               
                       )                                               
                      => Record t0 -> JSX                              
             to argument { classNameBad: "foo"
                         }                    
           while checking that expression div { classNameBad: "foo"
                                              }                    
             has type JSX
           in value declaration test
           
           where t0 is an unknown type
                 t1 is an unknown type
           
           See https://github.com/purescript/documentation/blob/master/errors/TypesDoNotUnify.md for more information,
           or to contribute content related to this error.
           
    
      To rerun use: --match "/compiler/Failing examples/'UnionRowErrors.purs' should fail to compile/"
    
    Randomized with seed 1464267016
    

    In this specific case, I think the ideal solution is to print the type alias everywhere where the long row appears. Due to desugaring our AST before typechecking it, we lose that information.

    I think the next best thing we could do is deduplicate the rows. In other words, look through the resulting error message, find all rows, and if they are the same, extract that out to a variable of sorts and reference that variable instead. For example, rather than the above error, we might get:

    Could not unify
    
      ( classNameBad :: String
      | t1
      )
    with type
      $$RowType
       
    while solving type class constraint
    
      Prim.Row.Union 
        ( classHead :: String )
        t1
        $$RowType
        
    while applying a function div
      of type Union @Type t0 t1 $$RowType
      to argument { classNameBad: "foo"
                  }
    ...
    References in error:
      $$RowType =
        ( _arita :: Array String
        , ... the rest of the row
        )
    
  7. rhendric commented on Oct 31, 2023

    @rhendric
    Member

    Deduplication might not be all that helpful in less trivial cases. If the offending code was div { id: "my-div", classNameBad: "foo" }, then the first type that you've replaced with $$RowType would be all of Props_div without the id label, and the second one would still be all of Props_div. Maybe we could make the deduplicator smart enough to present the latter as (id :: String | $$RowType )?

    Here's another proposal: when a TypesDoNotUnify between an open row (row with variable tail) and a closed row is presented, and the closed row is above a certain size, and verbose messages are off, only show the labels in the closed row that are alphabetically nearby (±2, or something?) to the labels present in the open row. In this case, that would be something like:

      Could not match type
                                
        ( classNameBad :: String
        ...                     
        | t1                    
        )                       
                                
      with type
                                                        
        ( ...                                           
        , classID :: String                             
        , className :: String                           
          -- classNameBad does not match any labels here
        , colSpan :: Int                                
        , contentEditable :: Boolean                    
        ...                                             
        )                                               
                                                        
    
    while solving type class constraint
    

    (and then the types present in the hints remain super ugly, but is that important?)

  8. JordanMartinez commented on Nov 9, 2023

    @JordanMartinez
    Contributor

    Here's another proposal...

    That's better than what I came up. To clarify, the 'alphabetically nearby' approach is based on the first few characters in the label, correct? And if someone happened to be deleting the prefix part of a label (e.g. pineapple -> eapple), this approach would show a different set of labels (e.g. e-staring labels rather than apple-starting labels), correct?

  9. rhendric commented on Nov 10, 2023

    @rhendric
    Member

    Yeah, that's what I had in mind. It's not ideal if the error is in the initial letters.

    If the user did a typo (lcassName or lassName), hopefully seeing the error is enough to nudge them toward correcting it without needing to see the correct label.

    The somewhat more concerning case is if the user has misremembered an initial component (they think it's elementClassName, for example); here this approach doesn't help, and maybe we'd want to add a did-you-mean hint when applicable. (If this is the motivating case for that hint, though, I think we wouldn't want to use per-character edit distance to select the did-you-mean; I think it'd make more sense to split on word boundaries and look for common words among the expected labels.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions