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

Add analyzer warning about throw expressions passed to GetOrElse #466

Open
Mafii opened this issue Nov 12, 2021 · 8 comments
Open

Add analyzer warning about throw expressions passed to GetOrElse #466

Mafii opened this issue Nov 12, 2021 · 8 comments
Labels
area: Analyzer enhancement New feature or request

Comments

@Mafii
Copy link
Member

Mafii commented Nov 12, 2021

When calling GetOrElse(<alternative>) at the end of a option chain, sometimes developers forget to pass a method and pass the throw expression directly. This happens because the throw expression will statisfy any type for the compiler.

Example of code that compiles, seems fine, but will throw an exception in both None and Some cases:

Option.FromNullable(someStringThatCanBeNullable)
  .Where(value => value != "Hello")
  .GetOrElse(throw new Exception("Unexpected...."));

what the person probably wanted to do:

Option.FromNullable(someStringThatCanBeNullable)
  .Where(value => value != "Hello")
  .GetOrElse(() => throw new Exception("Unexpected...."));

This is also what I propose as refactoring that should be presented to the user (quick fix): Just add a () => in front of the throw expression.

@Mafii Mafii added area: Analyzer enhancement New feature or request labels Nov 12, 2021
@bash
Copy link
Member

bash commented Nov 12, 2021

I agree, that's something that I've seen done wrong quite a few times.

Of course, this may also be a hint that the API isn't as obvious as it could be.
That said, I don't have any better alternatives, so an analyzer is probably our best option here.

@FreeApophis
Copy link
Member

An alternative would be to remove the value overload and only accept a Func. In almost all cases except the primitive types, this would improve the runtime performance, because the Func will only be evaluated when really necessary.

@Mafii
Copy link
Member Author

Mafii commented Nov 15, 2021

An alternative would be to remove the value overload and only accept a Func. In almost all cases except the primitive types, this would improve the runtime performance, because the Func will only be evaluated when really necessary.

While that is a possibility, I think it would be noise in cases where variables or primitives are already present.

You could argue it's still better however, as you can't accidentially calculate the fallback value (if you call a method) instead of passing it as lazy. There is no good way to write an analyzer for that. I would not strongly oppose removing the value overload.

@bash
Copy link
Member

bash commented Nov 15, 2021

@Mafii Are you sure that your first example compiles? I get a compiler error.

@Mafii
Copy link
Member Author

Mafii commented Nov 15, 2021

You're right, it does not compile - I sadly don't have the original anymore, and wrote this out of my head.

Here's one that does compile:

Funcky.Monads.Option.FromNullable(someStringThatCanBeNullable)
  .Where(value => value != "Hello")
  .GetOrElse(() => throw new Exception("Unexpected....")());

@Mafii
Copy link
Member Author

Mafii commented Nov 15, 2021

I found a more reasonable case where this behaviour can happen unexpectedly:

string? someStringThatCanBeNullable = null;
string? nullableFallbackValue = null;
Funcky.Monads.Option.FromNullable(someStringThatCanBeNullable)
  .Where(value => value != "Hello")
  .GetOrElse(nullableFallbackValue ?? throw new Exception("Unexpected...."));

The same can happen with ternary expressions, and all other cases where throw expressions can be part of an expression that directly returns a value (none on the top of my head but there might be some)

@Mafii
Copy link
Member Author

Mafii commented Nov 15, 2021

Here's some possible rules I have in mind for the analyzer to show a warning (or not):

  1. value passed to GetOrElse is a Func<T> or a method group -> show no warning
  2. value passed is not Func<T> or a method group and contains a throw statement -> show warning
  3. value passed is the result of a method call -> show warning (unless 4. is true)
  4. value passed is the return value of a method that is called, but the method returns a type statisfying Point 1 -> show no warning

@FreeApophis
Copy link
Member

See #521 and coordinate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Analyzer enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants