Skip to content

Statically resolved type parameters don't propagate correctly, and hashed typenames like ^?9104 in unjust compile error #3508

Description

@abelbraaksma

I must admit, I have seen this error before, but I have always managed to somehow mysteriously fix it, but now I'm stymied and I decided to report it.

The issue is: compile errors are raised on missing type parameters, but the error shows a garbled name which makes it virtually impossible to understand what is going on.

In addition, in this particular scenario, the error is incorrect, I think this source code should compile.

Repro steps

To repro, just copy the following code and try to compile, the error is on the last line.

type MyByte = MyByte of byte with
    static member MinValue = MyByte Byte.MinValue
    static member MaxValue = MyByte Byte.MaxValue
    static member op_Explicit (MyByte x): int64 = int64 x
    static member op_Explicit (MyByte x): double = double x

let inline isWithinMinMaxRange< ^b, ^a when 
                    ^b: comparison 
                    and ^a: comparison 
                    and ^b: (static member MinValue: ^b) 
                    and ^b: (static member MaxValue: ^b)
                    and ^b : (static member op_Explicit: ^b -> ^a) > 
                    (value: ^a) =

    let minRange = ((^b) : (static member MinValue: ^b) ())
    let maxRange = ((^b) : (static member MaxValue: ^b) ())
    let cast v = (^b : (static member op_Explicit: ^b -> ^a) v)

    value > cast minRange && value < cast maxRange

// compiles and runs fine, returns false
let test() = 
    isWithinMinMaxRange<MyByte, _> 256.0 
    
// Does not compile, FS0001 and FS0043 on the last line
let test<  ^source, ^value when 
        ^source: comparison 
        and ^value: comparison 
        and ^source: (static member MinValue: ^source) 
        and ^source: (static member MaxValue: ^source)
        and ^source : (static member op_Explicit: ^source -> ^value)> (v: ^value) =
    isWithinMinMaxRange< ^source, ^value> v     // error FS0001/FS0043: A type parameter is missing a constraint 'when  ^source : (static member op_Explicit :  ^source ->  ^?9104)'

Expected behavior

This should either compile just fine, or give an error that shows specifically what type parameter constraint is missing. The hashed names should not be shown. However, in this case, I don't think there's a constraint missing, as the call-site has equal type parameter constraints as the definition.

Actual behavior

It throws both FS0001 and FS0043, with equal texts:

error FS0001: A type parameter is missing a constraint 'when ^source : (static member op_Explicit : ^source -> ^?9104)'

and
error FS0043: A type parameter is missing a constraint 'when ^source : (static member op_Explicit : ^source -> ^?9104)'

Known workarounds

None, unfortunately (except for dynamic type resolution, but that is not an option here).

Related information

Activity

  1. changed the title [-]Statically resolved type parameters don't propagate correctly, nasty unresolvable and unnecessary compile error: FS0001: A type parameter is missing a constraint 'when ^source : (static member op_Explicit : ^source -> ^?9104)'[/-] [+]Statically resolved type parameters don't propagate correctly, and hashed typenames in compile error, seemingly impossible to understand or resolve[/+] on Aug 26, 2017
  2. changed the title [-]Statically resolved type parameters don't propagate correctly, and hashed typenames in compile error, seemingly impossible to understand or resolve[/-] [+]Statically resolved type parameters don't propagate correctly, and hashed typenames like ^?9104 in compile error[/+] on Aug 26, 2017
  3. changed the title [-]Statically resolved type parameters don't propagate correctly, and hashed typenames like ^?9104 in compile error[/-] [+]Statically resolved type parameters don't propagate correctly, and hashed typenames like ^?9104 in unjust compile error[/+] on Aug 26, 2017
  4. dsyme commented on Aug 29, 2017

    @dsyme
    Contributor

    @abelbraaksma Is there a repro without using op_Explicit constraints? thanks

  5. added
    Impact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.
    on Aug 29, 2017
  6. abelbraaksma commented on Aug 30, 2017

    @abelbraaksma
    ContributorAuthor

    @dsyme, yes, it happens with basically any method. But perhaps more likely with op_Explicit because that is usually used to be able to create an explicit cast (or, in other words, a member that has a different return type but the same parameters, which I believe is not allowed with any other operator, except op_Implicit).

    Example with op_Implicit:

    type MyByte = MyByte of byte with
        static member MinValue = MyByte Byte.MinValue
        static member op_Implicit (MyByte x): int64 = int64 x
        static member op_Implicit (MyByte x): double = double x
    
    let inline isWithinMinMaxRange< ^b, ^a when 
                        ^b: comparison 
                        and ^a: comparison 
                        and ^b: (static member MinValue: ^b) 
                        and ^b : (static member op_Implicit: ^b -> ^a) > 
                        (value: ^a) =
    
        let minRange = ((^b) : (static member MinValue: ^b) ())
        let cast v = (^b : (static member op_Implicit: ^b -> ^a) v)
    
        value > cast minRange
    
    // Does not compile, FS0001 and FS0043 on the last line
    let inline test<  ^source, ^value when 
            ^source: comparison 
            and ^value: comparison 
            and ^source: (static member MinValue: ^source)
            and ^source : (static member op_Implicit: ^source -> ^value) > (v: ^value) =
        isWithinMinMaxRange< ^source, ^value> v   // error FS001 and FS0043 shown here with same text
    

    Error:

    error FS0001: A type parameter is missing a constraint 'when ^source : (static member op_Implicit : ^source -> ^?9809)'
    error FS0043: A type parameter is missing a constraint 'when ^source : (static member op_Implicit : ^source -> ^?9809)'

    Example with a regular method (this code, however, is deliberately wrong, but it shows the error is faulty, or at the very least hard to understand):

    type MyByte = MyByte of byte with
        static member MinValue = MyByte Byte.MinValue
        static member Foo (MyByte x): int64 = int64 x
    
    let inline isWithinMinMaxRange< ^b, ^a when 
                        ^b: comparison 
                        and ^a: comparison 
                        and ^b: (static member MinValue: ^b) 
                        and ^b : (static member Foo: ^b -> ^a) > 
                        (value: ^a) =
    
        let minRange = ((^b) : (static member MinValue: ^b) ())
        let cast v = (^b : (static member Foo: ^b -> ^a) v)
    
        value > cast minRange
    
    // Does not compile, FS0001 and FS0043 on the last line
    let inline test<  ^source, ^value when 
            ^source: comparison 
            and ^value: comparison 
            and ^source: (static member MinValue: ^source)> (v: ^value) =
        isWithinMinMaxRange< ^source, ^value> v
    

    Error:

    error FS0001: A type parameter is missing a constraint 'when ^source : (static member Foo : ^source -> ^?9796)'
    error FS0043: A type parameter is missing a constraint 'when ^source : (static member Foo : ^source -> ^?9796)'

    Of course, in the latter example, the member constraint is actually missing in the declaration of test. However, it shows that a strange/wrong error is shown.

    It helps perhaps to show what I tried to achieve. I wanted to create a generic function with statically resolved type parameters, that can do a range check (and some other checks, removed for brevity) on a given type. Just some ducktyping, essentially. But for ease of use, I also hoped to be able to parameterize only the source type, as the target type would be known by the compiler from its argument.

    Here's what I thought ought to work, the original idea (note the missing type declarations on the declaration of test):

    type MyByte = MyByte of byte with
        static member MinValue = MyByte Byte.MinValue
        static member op_Explicit (MyByte x): int64 = int64 x
        static member op_Explicit (MyByte x): double = double x
    
    let inline isWithinMinMaxRange< ^b, ^a when 
                        ^b: comparison 
                        and ^a: comparison 
                        and ^b: (static member MinValue: ^b) 
                        and (^b) : (static member op_Explicit: ^b -> ^a) > 
                        (value: ^a) =
    
        let minRange = ((^b) : (static member MinValue: ^b) ())
        let cast v = (( ^b) : (static member op_Explicit: ^b -> ^a) v)
    
        value > cast minRange
    
    let inline test v =
        isWithinMinMaxRange<MyByte, _> v
    

    I think this code should compile, but it throws a compile error on the last line:

    error FS0043: The type 'MyByte' does not support a conversion to the type ''a'

    Which made add such method (a dummy to do a conversion to an unknown target type), which ultimately led to #3509 (crash of FSC). I think, still, that the code above is valid and that the FS0043 error should not be raised.

    I have meanwhile found a workaround:

    Since such code is only possible with using either op_Explicit or op_Implicit (because of limitations on return type variance), I noticed that, even if you only need a conversion in one direction (type ^b to ^a in this example), you must use the or syntax with both types to get it to work, as in (^a or ^b) .....

    So, given the same example, this works:

    type MyByte = MyByte of byte with
        static member MinValue = MyByte Byte.MinValue
        static member op_Explicit (MyByte x): int64 = int64 x
        static member op_Explicit (MyByte x): double = double x
    
    let inline isWithinMinMaxRange< ^b, ^a when 
                        ^b: comparison 
                        and ^a: comparison 
                        and ^b: (static member MinValue: ^b) 
                        and (^a or ^b) : (static member op_Explicit: ^b -> ^a) > 
                        (value: ^a) =
    
        let minRange = ((^b) : (static member MinValue: ^b) ())
        let cast v = ((^a or ^b) : (static member op_Explicit: ^b -> ^a) v)
    
        value > cast minRange
    
    let inline test v =
        isWithinMinMaxRange<MyByte, _> v 
    

    It compile fine and runs fine when used.

    That still leaves us with the two findings here:

    • An unintelligible error (two of them, actually)
    • Code that should compile but doesn't (and you may argue that the compiler issues a warning on op_Explicit and that this is what can happen, the same problem happens with op_Implicit, which doesn't give the warning, but shows the same problems).
  7. added this to the Unknown milestone on Aug 25, 2018
  8. abelbraaksma commented on Nov 28, 2018

    @abelbraaksma
    ContributorAuthor

    Just checking if my bug is still valid. As of 15.9.2, the behavior with the unintelligible error is still apparent, so yes:

    image

  9. brianberns commented on Sep 17, 2021

    @brianberns

    I see that this has been in the backlog for a while, and just want to bump it up if possible. I find that I run into it very easily when I try to do basic generic arithmetic, such as:

    let inline plus< ^a, ^b, ^c when (^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)> (x : ^a) (y : ^b) =
        x + y   // A type parameter is missing a constraint 'when ( ^a or  ^?1493953) : (static member ( + ) :  ^a *  ^?1493953 ->  ^?1493954)'
    
  10. dsyme commented on Nov 5, 2021

    @dsyme
    Contributor

    Treating this as a duplicate of #385

  11. abelbraaksma commented on May 16, 2024

    @abelbraaksma
    ContributorAuthor

    Reopening this one, as the duplicate was resolved, but that resolution did not fix this issue. The original code still raises the weird numbered types in exceptions.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions