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

Better betterness #484

Merged
merged 10 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion standard/delegates.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ A method or delegate type `M` is ***compatible*** with a delegate type `D` if al

- `D` and `M` have the same number of parameters, and each parameter in `D` has the same `ref` or `out` modifiers as the corresponding parameter in `M`.
- For each value parameter (a parameter with no `ref` or `out` modifier), an identity conversion ([§10.2.2](conversions.md#1022-identity-conversion)) or implicit reference conversion ([§10.2.8](conversions.md#1028-implicit-reference-conversions)) exists from the parameter type in `D` to the corresponding parameter type in `M`.

- For each `ref` or `out` parameter, the parameter type in `D` is the same as the parameter type in `M`.
- An identity or implicit reference conversion exists from the return type of `M` to the return type of `D`.

Expand Down
43 changes: 21 additions & 22 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -945,34 +945,33 @@ In case the parameter type sequences `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂

#### 11.6.4.4 Better conversion from expression

Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a ***better conversion*** than `C₂` if at least one of the following holds:
Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a better conversion than `C₂` if one of the following holds:
- `E` exactly matches `T₁` and `E` does not exactly match `T₂` (§11.6.4.5)
- `E` exactly matches both or neither of `T₁` and `T2`, and `T₁` is a better conversion target than `T2` (§11.6.4.6)
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

- `E` has a type `S` and an identity conversion exists from `S` to `T₁` but not from `S` to `T₂`
- `E` is not an anonymous function and `T₁` is a better conversion target than `T₂` ([§11.6.4.6](expressions.md#11646-better-conversion-target))
- `E` is an anonymous function, `T₁` is either a delegate type `D₁` or an expression tree type `Expression<D₁>`, `T₂` is either a delegate type `D₂` or an expression tree type `Expression<D₂>` and one of the following holds:
- `D₁` is a better conversion target than `D₂`
- `D₁` and `D₂` have identical parameter lists, and one of the following holds:
- `D₁` has a return type `Y₁`, and `D₂` has a return type `Y₂`, an inferred return type `X` exists for `E` in the context of that parameter list ([§11.6.3.13](expressions.md#116313-inferred-return-type)), and the conversion from `X` to `Y₁` is better than the conversion from `X` to `Y₂`
- `E` is async, `D₁` has a return type `Task<Y₁>`, and `D₂` has a return type `Task<Y>`, an inferred return type `Task<X>` exists for `E` in the context of that parameter list ([§11.6.3.13](expressions.md#116313-inferred-return-type)), and the conversion from `X` to `Y₁` is better than the conversion from `X` to `Y₂`
- `D₁` has a return type `Y`, and `D₂` is void returning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current Standard we have delegate return type of Y, presumably non-void, being a better conversion than a return type of void. I don’t immediately see this case exists in the revised model, is this intentional? A breaking change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now found the void case is in the source material Better betterness so I guess this was unintentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is intentional, and it was a regression in the original Better Betterness spec. The issue is dotnet/roslyn#6750. There is still work to do here, because I think this loses some other requirements.

Copy link
Contributor

@jskeet jskeet Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case that I think is broken by this:

void M(Action action) {}
void M(Func<object> func) {}

void Test()
{
    // "xyz" doesn't *exactly match* object, so this should be ambiguous (according to the PR)
    M(() => "xyz");
}

Case that I think isn't:

void M(Action action) {}
void M(Func<object> func) {}

void Test()
{
    // new object() exactly matches object, therefore Func<object> is preferred
    M(() => new object());
}

### 11.6.4.5 Exactly matching Expression
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

#### 11.6.4.5 Better conversion from type
Given an expression `E` and a type `T`, `E` ***exactly matches*** `T` is one of the following holds:
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

Given a conversion `C₁` that converts from a type `S` to a type `T₁`, and a conversion `C₂` that converts from a type `S` to a type `T₂`, `C₁` is a *better conversion* than `C₂` if at least one of the following holds:
- `E` has a type `S`, and an identity conversion exists from `S` to `T`
- `E` is an anonymous function, `T` is either a delegate type `D` or an expression tree type `Expression<D>` and one of the following holds:
- An inferred return type `X` exists for `E` in the context of the parameter list of `D` (§11.6.3.12), and an identity conversion exists from `X` to the return type of `D`
- Either `E` is non-async and `D` has a return type `Y` or `E` is async and `D` has a return type `Task<Y>`, and one of the following holds:
jskeet marked this conversation as resolved.
Show resolved Hide resolved
- The body of `E` is an expression that exactly matches `Y`
- The body of `E` is a statement block where every return statement returns an expression that exactly matches `Y`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need "statement block" rather than just "block"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @RexJaeschke to check if "statement block" or "block" is better.

From Jon: There are two other occurrences of "statement block" that should be changed. Jon will create an issue for that change.


- An identity conversion exists from `S` to `T₁` but not from `S` to `T₂`
- `T₁` is a better conversion target than `T₂` ([§11.6.4.6](expressions.md#11646-better-conversion-target))
### 11.6.4.6 Better conversion target
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

#### 11.6.4.6 Better conversion target
Given two different types `T₁` and `T₂`, `T₁` is a ***better conversion target*** than `T₂` if:
BillWagner marked this conversation as resolved.
Show resolved Hide resolved

Given two different types `T₁` and `T₂`, `T₁` is a better conversion target than `T₂` if at least one of the following holds:

- An implicit conversion from `T₁` to `T₂` exists, and no implicit conversion from `T₂` to `T₁` exists
- `T₁` is a signed integral type and `T₂` is an unsigned integral type. Specifically:
- `T₁` is `sbyte` and `T₂` is `byte`, `ushort`, `uint`, or `ulong`
- `T₁` is `short` and `T₂` is `ushort`, `uint`, or `ulong`
- `T₁` is `int` and `T₂` is `uint`, or `ulong`
- `T₁` is `long` and `T₂` is `ulong`
- An implicit conversion from `T₁` to `T₂` exists
- In case of a method group conversion (§10.6) for the corresponding argument, if a better conversion target (§7.5.3.5), is a delegate type that is not compatible (§19.4) with the single best method from the method group (§10.6), then neither delegate type is better.
jskeet marked this conversation as resolved.
Show resolved Hide resolved
- `T₁` is `Task<S₁>`, `T₂` is `Task<S₂>`, and `S₁` is a better conversion target than `S₂`
- `T₁` is `S₁` or `S₁`? where `S₁` is a signed integral type, and `T₂` is `S₂` or `S₂`? where `S₂` is an unsigned integral type. Specifically:
BillWagner marked this conversation as resolved.
Show resolved Hide resolved
- `S₁` is `sbyte` and `S₂` is `byte`, `ushort`, `uint`, or `ulong`
- `S₁` is `short` and `S₂` is `ushort`, `uint`, or `ulong`
- `S₁` is `int` and `S₂` is `uint`, or `ulong`
- `S₁` is `long` and `S₂` is `ulong`

#### 11.6.4.7 Overloading in generic classes

Expand Down