Upcast mandatory when there are different overload
Asked Answered
R

1

8

This is not about windows forms at all it's here only for the "background".

I was toying around Windows Forms when I got an error on an AddRange for a MenuStrip.Items requiring to cast ToolStripMenuItem into ToolStripItem

But I already have an AddRange for a Form.Controls before which didn't require casts.

After a little experimentation I managed to find that the error occurs when there are multiple overload for that AddRange so I tried to validate my thought :

type Foo () = class end
type Bar () = inherit Foo ()

type FooCollection () = class end // not really necessary

type Test1 () =
  member __.AddRange (col: FooCollection) = () // could be an int or anything instead
  member __.AddRange (foos: Foo []) = ()

type Test2 () = member __.AddRange (foos: Foo []) = ()

let lst1, lst2 = Test1 (), Test2 ()
lst1.AddRange [|Bar ()|] // error: have to explicitely cast => [|Bar () :> Foo|]
lst2.AddRange [|Bar ()|] // works

The question is simply why ; from my point of view the call is not ambiguous

Rickrickard answered 7/7, 2016 at 11:37 Comment(7)
For me the question is why it works in test2, if you make the method static it stops working.Athelstan
Test2 works for me with static method (Test1 still don't work obviously)Rickrickard
Can't edit the previous here is a sample from ideone for the static testRickrickard
Yes, I was testing with the wrong fsi session :) It also works with let bindings, so it's somehow bypassing F# type rules, since if you do [|Bar ()|] :> Foo[] it doesn't work.Athelstan
let binding ? you mean let foos : Foo [] = [| Bar () |] ? it's a wild guess but that works because it's another use of flexibility we give the type and there is an upcast between the two ; in [| Bar ()|] :> Foo [] it's the whole array we try to upcastRickrickard
@Gustavo - it's not bypassing F# type rules; consider ([| "test" |] : obj[]) vs. ([| "test" |] :> obj[]). See https://mcmap.net/q/1473077/-obj-and-string-as-parameters.Annatto
The way that arguments are elaborated differs between overloaded and non-overloaded methods. It's unclear to me to what extent this is intentional and specced, but see e.g. my edit in this answer for another place where it can occur.Annatto
R
2

After reading the 14.4.3 F# spec (hinted by Gustavo, kudos to him)

The F# compiler determines whether to insert flexibility after explicit instantiation, but before any arguments are checked.

I understand that flexibility is never inserted for a method which have overloads because it would need argument checking to choose.

Rickrickard answered 7/7, 2016 at 12:55 Comment(4)
I don't think flexibility is involved; the question isn't whether you can pass a Bar[] to a function expecting a Foo[] (you can't - try it with a non-literal), it's whether to treat the literal itself as an instance of Bar[] or Foo[].Annatto
From my (limited) understanding : When you have a non-literal the type is already "frozen" so it's the right one or not. end of story. When you have a non literal the type isn't "frozen" (to not say flexible) it depend on context. For a single method the context is clear so "an attempt is made to conform the parameter type to the argument type" (my way of describing it). For an overloaded method the context isn't that clear so the compiler doesn't bother trying attempt it has to be the developper to make it's intent clearer.Rickrickard
Yes, I think that's basically right - but that's not what the insertion of flexibility mentioned in 14.4.3 is about. Instead, that is strictly about accepting arguments of types which are statically known to be of subtypes of the method's argument types, which doesn't apply in this case.Annatto
I hope someone will come to enlighten us ; meanwhile I keep the answer to keep those comments.Rickrickard

© 2022 - 2024 — McMap. All rights reserved.