Skip to content
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

ToString() throws an exception for ValueOption<_>when value is ValueNone #7693

Closed
rca22 opened this issue Oct 3, 2019 · 14 comments · Fixed by #7712
Closed

ToString() throws an exception for ValueOption<_>when value is ValueNone #7693

rca22 opened this issue Oct 3, 2019 · 14 comments · Fixed by #7712
Labels
Area-Library Issues for FSharp.Core not covered elsewhere Bug
Milestone

Comments

@rca22
Copy link

rca22 commented Oct 3, 2019

The following code throws

let x : ValueOption<int> = ValueNone
let y = x.ToString() 

System.InvalidOperationException: ValueOption.Value
   at Microsoft.FSharp.Core.FSharpValueOption`1.get_Value()
   at Microsoft.FSharp.Core.FSharpValueOption`1.ToString()
   at Microsoft.FSharp.Core.PrintfImpl.FinalFast1@264.Invoke(FSharpFunc`2 env, A a)
   at <StartupCode$FSI_0013>.$FSI_0013.main@()

The behaviour should be comparable to the option type where

let x : Option<int> = None
let y = x.ToString()

Does not throw and sets y = "<null>"

This is a real pain when logging value in text files, as you expect sprintf "%O" to work for all F# types. Perhaps you could simply override .ToString() for ValueOption?

This is FSharp.Core 4.6.2, .Net Framework 4.8

@abelbraaksma
Copy link
Contributor

Out of curiosity, does string x give the same error? And what about `sprintf "%o" x"? Imo this is probably a bug (or a regression if it worked before).

@rca22
Copy link
Author

rca22 commented Oct 4, 2019 via email

@cartermp cartermp added Area-Library Issues for FSharp.Core not covered elsewhere Bug labels Oct 8, 2019
@cartermp cartermp added this to the Backlog milestone Oct 8, 2019
@abelbraaksma
Copy link
Contributor

If I understand the related linked issues correctly, x.ToString() should throw if it is supposed to be behave the same as option, but string x should not.

The question remains what we should print for ValueNone. And perhaps it is possible not to throw on x.ToString(), after all ValueNone is not null.

@rca22
Copy link
Author

rca22 commented Oct 9, 2019

@abelbraaksma: you've misunderstood. x.ToString() should never throw, because for a discriminated union like ValueOption, all values are valid. string x should also never throw as a result - this will end up calling the ToString() member. I would suggest printing "<null>" for ValueNone, so that the behaviour is identical to Option<_>

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Oct 9, 2019

@rca22, I should have been clearer that I tried to summarize the discussions in the other thread.

I don't agree with "<null>", as a value can never be null.

I think full parity is not possible, because currently, option with None throws. And you're suggesting it shouldn't. Or we should change the behavior for options as well.

@rca22
Copy link
Author

rca22 commented Oct 9, 2019

@abelbraaksma No: option with None does not throw - as I put in my original email. The ValueOption behaviour should match that, IMO.

@cartermp
Copy link
Contributor

cartermp commented Oct 9, 2019

The current PR gives "ValueNone" as per here: https://github.com/dotnet/fsharp/pull/7712/files#diff-1ff43febb9aa3f01fb97440321fb1507R226-R228

This is because Option compiles the None case down into a null, but ValueOption does not because it is a struct.

@rca22
Copy link
Author

rca22 commented Oct 9, 2019 via email

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Oct 11, 2019

No: option with None does not throw - as I put in my original email. The ValueOption behaviour should match that, IMO.

@rca22, not on my current version of VS 2019, 16.3.1, which I think is very recent. I don't know how you tested it, but I cannot repro the non-error scenario:

> None.ToString();;
System.NullReferenceException: Object reference not set to an instance of an object.
   at <StartupCode$FSI_0017>.$FSI_0017.main@()
Stopped due to error

> let x = None in x.ToString();;
System.NullReferenceException: Object reference not set to an instance of an object.
   at <StartupCode$FSI_0019>.$FSI_0019.main@()
Stopped due to error

> sprintf "%O" None;;
val it : string = "<null>"

> sprintf "%A" None;;
val it : string = "<null>"

> ValueNone.ToString();;
System.InvalidOperationException: ValueOption.Value
   at Microsoft.FSharp.Core.FSharpValueOption`1.get_Value() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2934
   at Microsoft.FSharp.Core.FSharpValueOption`1.ToString() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2951
   at <StartupCode$FSI_0020>.$FSI_0020.main@()
Stopped due to error

> let x = ValueNone in x.ToString();;
System.InvalidOperationException: ValueOption.Value
   at Microsoft.FSharp.Core.FSharpValueOption`1.get_Value() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2934
   at Microsoft.FSharp.Core.FSharpValueOption`1.ToString() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2951
   at <StartupCode$FSI_0021>.$FSI_0021.main@()
Stopped due to error

> sprintf "%O" ValueNone;;
System.InvalidOperationException: ValueOption.Value
   at Microsoft.FSharp.Core.FSharpValueOption`1.get_Value() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2934
   at Microsoft.FSharp.Core.FSharpValueOption`1.ToString() in E:\A\_work\159\s\src\fsharp\FSharp.Core\prim-types.fs:line 2951
   at Microsoft.FSharp.Core.PrintfImpl.FinalFast1@259.Invoke(FSharpFunc`2 env, A a) in E:\A\_work\159\s\src\fsharp\FSharp.Core\printf.fs:line 262
   at <StartupCode$FSI_0022>.$FSI_0022.main@()
Stopped due to error

> sprintf "%A" ValueNone;;
val it : string = "ValueNone"

In other words, currently, only sprintf appears to not throw for options, but throws for value options (unless you specify %A). I'm not saying we should keep the exceptions, but both currently throw for None or ValueNone.

@rca22
Copy link
Author

rca22 commented Oct 14, 2019

@abelbraaksma apologies for the belated response. I tried the exact same code for testing x.ToString() when x is Option<int>.None on my current version of VS which is 16.2.4 and agree that it fails, as per your last email.

> let x : int option = None;;
val x : int option = None
> let y = x.ToString();;
System.NullReferenceException: Object reference not set to an instance of an object.
   at <StartupCode$FSI_0003>.$FSI_0003.main@()
Stopped due to error
> let y = string x;;
val y : string = ""

> let y = sprintf "%O" x;;
val y : string = "<null>"

Whereas for ValueOption all of these fail. Apologies for the incorrect statement in my initial email - I must have copied the results for Option down incorrectly.

IMO the NullReferenceException thrown by Option is a bug in the compiler optimisation to use null, and shouldn't happen either! After all, if I defined something logically identical, type A<'x> = | B | C of 'x I can't imagine that would end up throwing exceptions for B...

@abelbraaksma
Copy link
Contributor

While I agree it is sensible to expect no error, ToString is an overridden member, and requires an instance, hence it doesn't work. Though in F# we shouldn't have nulls and I think it is fair to expect this to work without exception.

@rca22
Copy link
Author

rca22 commented Oct 14, 2019

Good point...

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Oct 31, 2019

@KevinRansom, can we summarize what the resolution was, considering this discussion went in several directions? I.e., ideally it never throws now on either printfn family of functions and .ToString(), but are these what this PR solved or only the former?

@cartermp cartermp modified the milestones: Backlog, 16.5 Jan 17, 2020
@abelbraaksma
Copy link
Contributor

Just happened to hit this issue again, so let me answer my own question, in case others are wondering what was implemented:

  • string ValueNone gets printed as "ValueNone"
  • ValueNone.ToString() does not throw anymore
  • string(ValueSome 42) gets printed as "ValueSome 42"
  • string None prints "" (empty string)
  • string(Some 42) prints "Some 42"
  • None.ToString() still raises an error (though the code path here suggests this should be "null")
  • Inside FSI, if a value is of type option and has value None, this will be shown as "val it : int option = None"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Library Issues for FSharp.Core not covered elsewhere Bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants