dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.86k stars 779 forks source link

Derived class not accepted for a type constraint when it implements `IEquatable<T>` #14020

Closed albertwoo closed 1 year ago

albertwoo commented 1 year ago

Please provide a succinct description of the issue.

Repro steps

Repo to reproduce: https://github.com/albertwoo/FSharpInheritIssue/blob/c805f56a2fad1e0313a09e85695e88c2bfef77f6/Program.fs#L10

image

For other types this kind of inheritance is ok, but it looks like the Xaml panel or control have some special thing which cause fsharp compiler cannot infer that.

Expected behavior

Should repot no error.

Actual behavior

Provide a description of the actual behaviour observed.

Known workarounds

None

Related information

Provide any related information (optional):

0101 commented 1 year ago

I believe the reason is because that Panel implements IEquatable<Panel> and VirtualizingPanel implements IEquatable<VirtulizingPanel>.

Since 'T must satisfy :> Panel for class A the constraint solver figures that IEquatable<Panel> should equal IEquatable<VirtualizingPanel> and this is where the error message actually comes from.

So the behavior is correct, because VirtualizingPanel does not satisfy this constraint, but the error message is quite confusing because the user doesn't know why the 2 types should be equal.

We should probably extend the error message with more context that would explain what's going on.

Minimal repro:


open System

type MyClass() =
    member this.x = 1
    interface IEquatable<MyClass> with
        member this.Equals _ = false

type MySubClass() =
    inherit MyClass()
    interface IEquatable<MySubClass> with
        member this.Equals _ = false

type A<'T when 'T :> MyClass>() =
    class end

type B<'T when 'T :> MySubClass>() =
    inherit A<'T>()
      inherit A<'T>();;
  ------------^^^^^

stdin(19,13): error FS0001: The type 'MyClass' does not match the type 'MySubClass'
albertwoo commented 1 year ago

@0101 I think it is not message confusing. There are two reasons:

  1. From the fsharp docs "The provided type must be equal to or derived from the type specified, or, if the type is an interface, the provided type must implement the interface.", So MySubClass is indeed derived from MyClass. So it should satisfy.
  2. Same thing works in csharp, and from the sytax shape it make sense.
    
    using System;

class MyClass : IEquatable { public int X { get; set; } public bool Equals(MyClass? other) => false; }

class MySubClass : MyClass, IEquatable { public bool Equals(MySubClass? other) => false; }

class A where T : MyClass { }

class B : A where T : MySubClass { }

0101 commented 1 year ago

@albertwoo I guess you are right, it should probably accept any derived class.

SteveGilham commented 1 year ago

This of course generalises to any such hierarchy based on an instance of the curiously recurring template pattern

type ISprintable<'T> =
    abstract member Print : format:'T -> unit

type BaseClass() =
  interface ISprintable<BaseClass> with
    member this.Print t = printfn "%A" t

type Derived() =
  inherit BaseClass()
  interface ISprintable<Derived> with
    member this.Print t = printfn "%A" t

type A<'T when 'T :> BaseClass>() =
  class
  end

type B<'T when 'T :> Derived>() =
  inherit A<'T>()

which results in

    inherit A<'T>();;
  ----------^^^^^

stdin(18,11): error FS0001: The type 'BaseClass' does not match the type 'Derived'

and not just the particular case of IEquatable<'T>

T-Gro commented 1 year ago

Strangely this works when using 2 generic parameters for B (provided by the same real type at instantiation), maybe this might give a hint on where the issue is. Can also act as a workaround for the time being ;; @albertwoo and @SteveGilham .

type ISprintable<'T> =
    abstract member Print : format:'T -> unit

type BaseClass() =
  interface ISprintable<BaseClass> with
    member this.Print t = printfn "%A" t

type Derived() =
  inherit BaseClass()
  interface ISprintable<Derived> with
    member this.Print t = printfn "%A" t

type A<'T when 'T :> BaseClass>() =
  class
  end

type B<'T,'U when 'T :> Derived and 'U:> BaseClass>() =
  inherit A<'U>()

let giveMeA = new A<BaseClass>()
let giveMeB = new B<Derived,Derived>()
T-Gro commented 1 year ago

Seeing more trough it, this is a duplicate of https://github.com/dotnet/fsharp/issues/12206 . And is covered by this (not implemented) lang suggestion https://github.com/fsharp/fslang-suggestions/issues/255