-
Notifications
You must be signed in to change notification settings - Fork 789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using string obj
vs obj.ToString() leads to problems: the type parameter can escape its scope
#7958
Comments
I have the habit of using |
The same error shows up if you use type Foo< ^T > =
| Bar of ^T
override this.ToString () = // FS0670
match this with
| Bar x -> x.ToString () Not sure if this is relevant, but if you hide type Baz< ^U > =
| Qux of ^U
member inline this.ToString () =
match this with
| Qux q -> string q |
I think it's relevant, because it suggests at why the error is happening in the first place. What you did was creating a new non-virtual member that cannot be overridden. Hence the compiler knows statically what types to compile I'm not 100% sure my assumptions are correct (@dsyme, any thoughts?). But if so, one resolution might be to treat I think that we somehow should make it possible that |
Another workaround: type Foo<'T> =
| Bar of 'T
override this.ToString() =
match this with
| Bar x -> string (box x) |
This is by design, since This must have been because there can in theory be differences between the behavior of The implementation of let inline anyToString nullStr x =
match box x with
| null -> nullStr
| :? System.IFormattable as f -> f.ToString(null,System.Globalization.CultureInfo.InvariantCulture)
| obj -> obj.ToString()
let inline string (value: ^T) =
anyToString "" value
// since we have static optimization conditionals for ints below, we need to special-case Enums.
// This way we'll print their symbolic value, as opposed to their integral one (Eg., "A", rather than "1")
when ^T struct = anyToString "" value
when ^T : float = (# "" value : float #).ToString("g",CultureInfo.InvariantCulture)
when ^T : float32 = (# "" value : float32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int64 = (# "" value : int64 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int32 = (# "" value : int32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int16 = (# "" value : int16 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : nativeint = (# "" value : nativeint #).ToString()
when ^T : sbyte = (# "" value : sbyte #).ToString("g",CultureInfo.InvariantCulture)
when ^T : uint64 = (# "" value : uint64 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : uint32 = (# "" value : uint32 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : int16 = (# "" value : int16 #).ToString("g",CultureInfo.InvariantCulture)
when ^T : unativeint = (# "" value : unativeint #).ToString()
when ^T : byte = (# "" value : byte #).ToString("g",CultureInfo.InvariantCulture) |
@dsyme, thanks for the insights. From the docs (for any numeric type):
The overload In other words, these all do exactly the same: string someFloat
someFloat.ToString(null, CultureInfo.InvariantCulture) // IFormattable
someFloat.ToString(CultureInfo.InvariantCulture) // overload, not IFormattable
(someFloat :> IFormattable).ToString(null, CultureInfo.InvariantCulture);; That leaves We could, therefor, simplify the code as follows without backward compat issues, and that'll allow the F# to continue using let anyToString nullStr x =
match box x with
| null -> nullStr
| :? System.IFormattable as f -> f.ToString(null,System.Globalization.CultureInfo.InvariantCulture)
| obj -> obj.ToString()
let string value =
anyToString "" value Wrt to enums, they will still print their symbolic value. I.e., after this change, Would such a PR be accepted? It will solve the problem with |
How about the other integer cases? Do they default to "g" as well? I suppose so?
Yes I think so - it's definitely a simpler experience if this is not statically resolved. That said, I don't recall us making a change from statically-resolved to non-statically resolved for such a basic function before, so there may be some subtlety. Perhaps push a PR and run it through tests as a first indication? |
@dsyme Yes, this is true for all,except the two native integers (but that's irrelevant, they already use the default).
I had the same feeling, but there's a way to find out.
That's the way ;) If tests check the generated IL they may fail, we'll see. Obviously, the compiled IL will differ. I'll make a PR and see what happens. |
The solution I chose in the referenced PR was eventually to remove the bug that the optimization paths were never chosen, and to improve and extend the optimization paths. In the top of the PR, you can find the performance benefit charts. Basically: it's between 10% gain and 900% gain,depending on input type. The error doesn't rise anymore. |
I was refactoring some old code, replacing
x.ToString()
withstring x
for readability and, let's face it, best practices. But much to my surprise, this didn't work in a number of cases, though I always thought the two were interchangeable (with the exception ofnull
, which is treated as""
forstring null
and as NRE fornull.ToString()
, hence the preference forstring
vs.ToString()
).It can lead to this:
But I don't think anything is "escaping its scope" here...
Repro steps
It is thrown by this code:
Expected behavior
It should compile. The
string
function is so basic to the language, it should be used wheneverx.ToString()
can be used.Actual behavior
Using
string
can raise the following compile error:Known workarounds
I found two workarounds:
string
and stick toToString()
, but that's a PITARelated information
Seen on any recent F# version. I did not (yet) test whether this is a regression.
The text was updated successfully, but these errors were encountered: