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

Dictionary expressions: remove PROTOTYPE comments #77876

Merged
merged 10 commits into from
Mar 31, 2025

Conversation

cston
Copy link
Member

@cston cston commented Mar 27, 2025

No description provided.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 27, 2025
@@ -907,8 +904,6 @@ private BoundCollectionExpression ConvertCollectionExpression(
{
var analyzedArguments = AnalyzedArguments.GetInstance();
withElement.AddToArguments(analyzedArguments);
// PROTOTYPE: If there are multiple with() elements, should with() elements after
Copy link
Member Author

@cston cston Mar 27, 2025

Choose a reason for hiding this comment

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

PROTOTYPE

We can bind for error recovery rather than binding a second constructor call if it's difficult to support the latter from the public API, for instance, from IOperation. Otherwise, binding constructor calls for each seems simpler.

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the bad constructor calls say they have errors in the operation tree, this seems fine. That's just us giving the full information about what we think the code means.

@@ -1004,8 +997,6 @@ private BoundCollectionExpression ConvertCollectionExpression(
if (collectionCreation is null)
{
// Bind collection creation with no arguments.
// PROTOTYPE: Should we require a factory method callable with no arguments even if arguments are provided
Copy link
Member Author

Choose a reason for hiding this comment

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

PROTOTYPE

Tracked with an open question.

@@ -6631,8 +6631,6 @@ static BoundCall bindKeyOrValue(SyntaxNode syntax, BoundExpression expr, MethodS
return new BoundCall(
syntax,
receiverOpt: expr,
// PROTOTYPE: What is the correct value for receiver cloning? Test with expression element
Copy link
Member Author

@cston cston Mar 27, 2025

Choose a reason for hiding this comment

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

PROTOTYPE

These BoundCall nodes are for accessors from a well-known struct type, KeyValuePair.Key<K, V> { get; } and KeyValuePair<K, V>.Value { get; }, so I don't think this applies.

Copy link
Contributor

Choose a reason for hiding this comment

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

Were the indicated tests added? It wasn't clear to me why these accessors being on well-known type affects what value we should pass here.

It seems like existing places in the binder where False is passed here, are taking care to ensure that the receiver is something we can take a reference to.

Copy link
Member Author

Choose a reason for hiding this comment

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

Switched to calling ReceiverIsSubjectToCloning(expr, getMethod), to avoid hardcoding the value. However, expr is a BoundValuePlaceholder so ReceiverIsSubjectToCloning() simply returns ThreeState.False.

Should expr be the original expression that the placeholder will be replaced with? cc @AlekseyTs

Copy link
Contributor

@AlekseyTs AlekseyTs Mar 29, 2025

Choose a reason for hiding this comment

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

Should expr be the original expression that the placeholder will be replaced with?

Strictly speaking, the value that we are supposed to pass to the BoundCall constructor for initialBindingReceiverIsSubjectToCloning parameter should reflect what is actually going to happen during code generation. I assume that during lowering, a key value pair will be captured in a temp and then the accessor will be invoked on that temp. Therefore, a cloning will happen, but not as part of IL emit for the node itself.
On the other hand, as far as I understand, the actual initialBindingReceiverIsSubjectToCloning value is interesting for ref safety analysis only. Does this node go through the analysis? If it doesn't, then passing ThreeState.Unknown would be the honest and the simplest thing to do. If it does, again, strictly speaking, we should pass ThreeState.True. However, it looks like GetInvocationArgumentsForEscape doesn't like placeholders as receivers and it assumes specific SafeContext value for the future temp, which might or might not reflect the actual SafeContext of the temp introduced during lowering. At the same time, we probably can assume that the KeyValuePair is not a ref struct and the accessor takes no refs and returns no refs, there should be no ref safety issues due to the cloning (a ref to that temp location cannot escape from the accessor), therefore, passing ThreeState.False would be correct (little explanation comment might be helpful for a future code reader).

In my opinion, calling ReceiverIsSubjectToCloning here with placeholder probably doesn't help anyone, and as a follow-up we probably should change it to either assert on placeholders, or return ThreeState.Unknown when type is not known to be a reference type.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the detailed explanation. I've switched back to ThreeState.False and added a comment from your explanation.

@@ -1473,8 +1475,6 @@ class Program
}
""";
comp = CreateCompilation([sourceA, sourceB2], targetFramework: TargetFramework.Net80);
// PROTOTYPE: Update the params-collection spec so it's clear that we require "... a factory method callable with no additional
Copy link
Member Author

Choose a reason for hiding this comment

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

PROTOTYPE

See dotnet/csharplang#9264.

@@ -16309,7 +16309,6 @@ static void Main()
}
""";
comp = CreateCompilation(sourceB, references: new[] { refA }, targetFramework: TargetFramework.Net80);
// PROTOTYPE: ERR_CollectionBuilderAttributeMethodNotFound error message is stale. It should say "... with a first parameter of type 'ReadOnlySpan<object> callable with no additional arguments, ...".
Copy link
Member Author

Choose a reason for hiding this comment

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

PROTOTYPE

The ReadOnlySpan<T> parameter should be last, not first, so the current message seems sufficient.

@cston cston marked this pull request as ready for review March 27, 2025 23:54
@cston cston requested review from a team as code owners March 27, 2025 23:54
Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 5)

@jcouv jcouv self-assigned this Mar 28, 2025
@cston cston requested a review from a team March 28, 2025 14:01
@RikkiGibson RikkiGibson self-assigned this Mar 28, 2025
@@ -6631,8 +6631,6 @@ static BoundCall bindKeyOrValue(SyntaxNode syntax, BoundExpression expr, MethodS
return new BoundCall(
syntax,
receiverOpt: expr,
// PROTOTYPE: What is the correct value for receiver cloning? Test with expression element
Copy link
Contributor

Choose a reason for hiding this comment

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

Were the indicated tests added? It wasn't clear to me why these accessors being on well-known type affects what value we should pass here.

It seems like existing places in the binder where False is passed here, are taking care to ensure that the receiver is something we can take a reference to.

@@ -907,8 +904,6 @@ private BoundCollectionExpression ConvertCollectionExpression(
{
var analyzedArguments = AnalyzedArguments.GetInstance();
withElement.AddToArguments(analyzedArguments);
// PROTOTYPE: If there are multiple with() elements, should with() elements after
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the bad constructor calls say they have errors in the operation tree, this seems fine. That's just us giving the full information about what we think the code means.

SyntaxKind.SpreadElement,
SyntaxKind.KeyValuePairElement,
SyntaxKind.WithElement,
SyntaxKind.WithElement, // Needed when language version is updated.
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 28, 2025

Choose a reason for hiding this comment

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

I didn't understand what this means. Is the with-element in TestResource.resx not detected at the moment? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

The parser only generates SyntaxKind.WithElement when compiling with LanguageVersion.Preview, but this test currently uses LanguageVersion.Latest. When Latest is updated, this case can be removed.

string sourceB = """
class Program
{
static MyDictionary<K, V> FromPair1<K, V>(K k, V v)
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 28, 2025

Choose a reason for hiding this comment

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

Some tests maybe worth adding after feature merge:

  • MyDictionary<K, V> d = [k:v]; return d;
  • FromPair(ref K k, ref V v) => [k:v]
  • MyDictionary<K, V> d = [MakeKey():v]; return d; #Resolved

// PROTOTYPE: [with(x)] and [with(x), y] should result in errors since x should not be included in the params argument.
// https://github.com/dotnet/roslyn/issues/77866: [with(x)] and [with(x), y] should
// result in errors since x should not be included in the params argument. Should
// be fixed when the last parameter of the builder method is the items parameter.
Copy link
Contributor

@RikkiGibson RikkiGibson Mar 28, 2025

Choose a reason for hiding this comment

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

Is this saying we're going to add some enforcement later which will avoid including the 'with' arguments for the last parameter of Create? It might be good to demonstrate what arguments are actually being passed for items in Create(items), e.g. with expectedOutput. But if it's thought to be too much of a corner case, we can just wait to take the fix instead. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I expect the change for parameter order will include some enforcement to catch cases where with arguments may be used for the last parameter.

@@ -6666,6 +6664,10 @@ private BoundExpression BindDictionaryItemAssignment(
return new BoundCall(
syntax,
receiverOpt: implicitReceiver,
// initialBindingReceiverIsSubjectToCloning is used for ref safety analysis, KeyValuePair<,> is
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 29, 2025

Choose a reason for hiding this comment

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

// initialBindingReceiverIsSubjectToCloning is used for ref safety analysis, KeyValuePair<,> is

It looks like this comment belongs in the previous local function instead #Closed

@cston cston merged commit 9705bdf into dotnet:features/dictionary-expressions Mar 31, 2025
28 checks passed
@cston cston deleted the dictionary-4 branch March 31, 2025 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature - Dictionary Expressions untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants