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

Align params type validation with the the spec #71918

Merged

Conversation

@AlekseyTs AlekseyTs requested a review from a team as a code owner February 1, 2024 20:21
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Feb 1, 2024
@AlekseyTs
Copy link
Contributor Author

@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review.

@RikkiGibson RikkiGibson self-assigned this Feb 1, 2024
@AlekseyTs
Copy link
Contributor Author

@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review.

2 similar comments
@AlekseyTs
Copy link
Contributor Author

@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review.

@AlekseyTs
Copy link
Contributor Author

@RikkiGibson, @333fred, @dotnet/roslyn-compiler Please review.

implicitReceiver,
out addMethods);

// This is what BindCollectionInitializerElementAddMethod is doing in terms of reporting diagnostics and detecting a failure
Copy link
Member

@cston cston Feb 6, 2024

Choose a reason for hiding this comment

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

BindCollectionInitializerElementAddMethod

This seems like a lot of code that needs to stay in sync with BindCollectionInitializerElementAddMethod. Is the reason for duplicating the code to handle cycles and return the add methods, and is there an easy way to use the existing Binder methods? For instance, if the reason is to handle cycles, could we use throw an exception at the point HasParamsCollectionTypeInProgress(namedType) is determined and have the outer most caller that created the ParamsCollectionTypeInProgressBinder catch that case? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Feb 6, 2024

Choose a reason for hiding this comment

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

There are several reasons. Main reasons are to reduce amount of binding and have a better control what is causing a failure. And cycles, of course. For example, in general we are not supposed to make semantic decisions based on BoundNode.HasErrors flag, but we did. We are trying to not set the flag too liberally, but that guideline is not always followed (especially in a very old code), because we are not supposed to use its value for semantic decisions. Also, I did find examples when some errors that are not supposed to fail conversion classification failed it, etc. I prefer to have a little code duplication, but have a better control over what we are doing. I do not expect the methods to change much in the future. In the process, in the new code I was able to drop a lot of original code that is not relevant to our scenarios and cause. Therefore, no need to think about "protecting" those code paths. The chosen approach gives me greater confidence that we are doing the right thing from behavior point of view.

if (!paramsType.IsSZArray())
{
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
emptyCollection ??= new BoundUnconvertedCollectionExpression(node, ImmutableArray<BoundNode>.CastUp(ImmutableArray<BoundExpression>.Empty)) { WasCompilerGenerated = true, IsParamsCollection = true };
Copy link
Member

@cston cston Feb 6, 2024

Choose a reason for hiding this comment

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

ImmutableArray.CastUp(ImmutableArray.Empty)

ImmutableArray<BoundNode>.Empty? #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Feb 6, 2024

Choose a reason for hiding this comment

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

No, we need this array of BoundExpression underneath, so that we could do a cast down without allocation/copy, if needed. Perhaps for an empty array, there isn't much to allocate and copy, but I prefer to be consistent

case CollectionExpressionTypeKind.ImplementsIEnumerable:
case CollectionExpressionTypeKind.CollectionBuilder:
{
binder.TryGetCollectionIterationType(CSharpSyntaxTree.Dummy.GetRoot(), type, out elementType);
Copy link
Member

@cston cston Feb 6, 2024

Choose a reason for hiding this comment

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

CSharpSyntaxTree.Dummy.GetRoot()

Consider extracting a local. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider extracting a local.

Done

}
}

void reportInvalidParams(BindingDiagnosticBag diagnostics)
Copy link
Member

@cston cston Feb 6, 2024

Choose a reason for hiding this comment

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

reportInvalidParams

Consider declaring this local function and the two above at the same scope. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider declaring this local function and the two above at the same scope.

Done

@@ -3707,30 +4937,12 @@ static void Main()

// Inline collection expression results in an ambiguity.
Copy link
Contributor

@RikkiGibson RikkiGibson Feb 6, 2024

Choose a reason for hiding this comment

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

Is this comment still accurate? I thought collection-expressions also made a change to make this conversion not applicable when the expected APIs are not present on the collection type. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this comment still accurate?

Will remove. Thanks.

var x = (params int a) => a;
local (0);

int local(params int a) => a;
Copy link
Contributor

@RikkiGibson RikkiGibson Feb 6, 2024

Choose a reason for hiding this comment

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

It's possible I missed it, but I did not see a test case which demonstrates the lack of accessibility checks on the constructor and Add method when the params parameter is in a local function or lambda. Please add such a test if one does not already exist, for example:

using System.Collections;
using System.Collections.Generic;

public class MyCollection : IEnumerable<string>
{
    internal MyCollection() { }
    internal void Add(string s) { }

    IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null!;
    IEnumerator IEnumerable.GetEnumerator() => throw null!;
}

public class Program
{
    public void Test1()
    {
        local();
        local("a");

        // expect no diagnostic
        void local(params MyCollection collection) { }

        // expect no diagnostic
        var x = (params MyCollection collection) => { };
    }

    // expect a declaration diagnostic for insufficient accessibility of MyCollection..ctor/Add()
    public void Test2(params MyCollection collection)
    {
    }
}
``` #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's possible I missed it, but I did not see a test case which demonstrates the lack of accessibility checks on the constructor and Add method when the params parameter is in a local function or lambda. Please add such a test if one does not already exist

Added ImplementsIEnumerableT_20_LessAccessibleConstructorAndAdd_NoError_In_LambdaOrLocalFunction

binder.ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false);
// NOTE: Use-site diagnostics were reported during overload resolution.

CheckRequiredMembersInObjectInitializer(method, initializers: default, node, diagnostics);
Copy link
Contributor

@RikkiGibson RikkiGibson Feb 6, 2024

Choose a reason for hiding this comment

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

Perhaps we should have a test for when the collection type has a required property. I would expect an error when using 'params' with such a type or when converting to the type from collection expression. But that the conversion would still exist. #Resolved

Copy link
Contributor Author

@AlekseyTs AlekseyTs Feb 7, 2024

Choose a reason for hiding this comment

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

Perhaps we should have a test for when the collection type has a required property. I would expect an error when using 'params' with such a type or when converting to the type from collection expression. But that the conversion would still exist.

Added tests, they behave as you expect, but I am not sure whether I am happy about that fact.

type: GetSpecialType(SpecialType.System_Void, diagnostics, elementInitializer),
hasErrors: hasErrors);
var d = BindingDiagnosticBag.GetInstance();
bool toCheck = collectionInitializerAddMethodBinder.HasCollectionExpressionApplicableAddMethod(elementInitializer, implicitReceiver.Type, boundElementInitializerExpressions[0].Type, addMethods: out _, d);
Copy link
Contributor

@RikkiGibson RikkiGibson Feb 6, 2024

Choose a reason for hiding this comment

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

Please add a comment to explain what this assert is checking in future PR #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please add a comment to explain what this assert is checking in future PR

Added a comment.

@AlekseyTs
Copy link
Contributor Author

@333fred, @dotnet/roslyn-compiler For the second review

1 similar comment
@AlekseyTs
Copy link
Contributor Author

@333fred, @dotnet/roslyn-compiler For the second review

@cston
Copy link
Member

cston commented Feb 7, 2024

    public void CreateMethod_10_NotExtensionGetEnumerator()

Is this distinct from CreateMethod_04_NoElementType? #Resolved


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1019 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@cston
Copy link
Member

cston commented Feb 7, 2024

        comp.VerifyEmitDiagnostics();

Consider executing this code, and verifying which Add() is invoked. #Resolved


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1885 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@cston
Copy link
Member

cston commented Feb 7, 2024

        comp.VerifyEmitDiagnostics();

Consider executing this code, and verifying which Add() is invoked. #Resolved


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1925 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@cston
Copy link
Member

cston commented Feb 7, 2024

}

Consider including:

Test([]);
Test([4]);
``` #Closed

---
Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:10458 in 9562c7c. [](commit_id = 9562c7c9baa1bc082c7e969a8cfd8eb04d26d4ee, deletion_comment = False)

@cston
Copy link
Member

cston commented Feb 7, 2024

}

Looks like these cases are covered in Cycle_04.


In reply to: 1932705423


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:10458 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

    public void CreateMethod_10_NotExtensionGetEnumerator()

Is this distinct from CreateMethod_04_NoElementType?

Looks like a dupe indeed.


In reply to: 1932682227


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1019 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

        comp.VerifyEmitDiagnostics();

Consider executing this code, and verifying which Add() is invoked.

The runtime behavior isn't something that I am interested in. We can "bind" creation and that is good enough for the purpose of the feature.


In reply to: 1932688118


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1885 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

        comp.VerifyEmitDiagnostics();

Consider executing this code, and verifying which Add() is invoked.

The runtime behavior isn't something that I am interested in. We can "bind" creation and that is good enough for the purpose of the feature.


In reply to: 1932688311


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1925 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

        comp.VerifyEmitDiagnostics();

To clarify, the process of creating the collection and adding elements to it is handled by Collection Expressions code


In reply to: 1932813913


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1925 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs
Copy link
Contributor Author

    public void CreateMethod_10_NotExtensionGetEnumerator()

Is this distinct from CreateMethod_04_NoElementType?

Looks like a dupe indeed.

Since I am not planning to make other changes in this PR. I am going to merge as is and get rid of the dupe in the next PR


In reply to: 1932710683


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1019 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

@AlekseyTs AlekseyTs merged commit 6cd3f1e into dotnet:features/ParamsCollections Feb 7, 2024
25 checks passed
Copy link
Contributor

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

Resolution of the PR comments looks good to me, thanks.

@AlekseyTs
Copy link
Contributor Author

    public void CreateMethod_10_NotExtensionGetEnumerator()

Removed the dupe in #72000


In reply to: 1932839926


Refers to: src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs:1019 in 9562c7c. [](commit_id = 9562c7c, deletion_comment = False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature - ParamsCollections 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.

3 participants