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

Collection expressions cannot be initialize if we specify the type #77056

Open
Mad-Lynx opened this issue Feb 5, 2025 · 6 comments
Open

Collection expressions cannot be initialize if we specify the type #77056

Mad-Lynx opened this issue Feb 5, 2025 · 6 comments

Comments

@Mad-Lynx
Copy link

Mad-Lynx commented Feb 5, 2025

Version Used: 9.0.100 (but as well v8)

Steps to Reproduce:

  1. Some object support collection expression
  2. Extend the object, so adding new Add method, that support some other type
  3. try to use that other type when initializing the collection.

A minimal repro in sharplab.

Expected Behavior:
You should be able to initialize the object just specifying the new() AND specifying the type new Rec()

[
    "42",
    new(42),
    new Rec(42), // that should not cause compiler error
]

Actual Behavior:
Specifying the type new Rec() is causing the compiler to complain...
But if we omit the type (and allow compiler to figure it out), it will be allowed.


With complex type, we can use "workaround" and not specify the type, there is no way we can do that with simple types.

The example is with "combination" (when we pass both types - strings, and ints/Recs), but the issue exists even if we will be trying to use one type.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Feb 5, 2025
@CyrusNajmabadi
Copy link
Member

public class ComplexCollection : StringCollection
{
    public void Add(Rec v) => Add(v.ToString());
}

ComplexCollection doesn't count as a collection here because it's iteration type is just 'string' (not 'Rec'). So you cannot add instances of 'Rec's to it. For that to be allowed, a language change would have to be made here. Such requests can happen at dotnet/csharplang. Thanks!

@CyrusNajmabadi CyrusNajmabadi closed this as not planned Won't fix, can't repro, duplicate, stale Feb 5, 2025
@dotnet-policy-service dotnet-policy-service bot removed the untriaged Issues and PRs which have not yet been triaged by a lead label Feb 5, 2025
@Mad-Lynx
Copy link
Author

Mad-Lynx commented Feb 5, 2025

If what you are saying is true...
why then

ComplexCollection com1 = [
    "42",
    new(42),
    ];

IS allowed, and it's translated correctly to

        ComplexCollection complexCollection = new ComplexCollection();
        complexCollection.Add("42");
        complexCollection.Add(new Rec(42));

But as soon as we will specify the type name near new - new Rec(42), - the complier complains.

Both collection expression and collection initializers seems producing the same code (in this instance).
So why using collection initializer

{
    "12",
    new(42),
    new Rec(42),
};

is allowed
but collection expression

[
    "42",
    new(42),
    //new Rec(42), // error CS0029: Cannot implicitly convert type 'Rec' to 'string'
];

not.

@RikkiGibson
Copy link
Contributor

It doesn't seem expected that adding a Rec is allowed, only if the new() form is used, and not new Rec(). I would expect either both forms to be disallowed or both to be permitted.

@CyrusNajmabadi CyrusNajmabadi reopened this Feb 6, 2025
@CyrusNajmabadi
Copy link
Member

Reactivating. Missed that. Tagging in @cston

@cston
Copy link
Member

cston commented Feb 6, 2025

This is a loophole in the collection expression spec.

The collection expression conversion requires each element to be implicitly convertible to the element type, but when constructing the collection, we use the applicable Add overload for the element expression.

For ComplexCollection, the element type of the collection is string.

ComplexCollection com1 = [
    "42",
    new(42),
    //new Rec(42), // error CS0029: Cannot implicitly convert type 'Rec' to 'string'
    ];

For the element new(42), there is an implicit object creation conversion from the expression to the collection element type string.

A target_typed_new expression does not have a type. However, there is a new object creation conversion that is an implicit conversion from expression, that exists from a target_typed_new to every type.

And for construction, new(42) is bound to the applicable Add(Rec) overload rather than the inapplicable Add(string) overload. In short, conversion and subsequent construction for new(42) succeeds.

However, for the explicitly typed element new Rec(42), there is no implicit conversion from the expression to the collection element type string, so conversion fails.

See also dotnet/csharplang#7724

It would be good to fix this spec loophole, and align conversion and construction, if we can do so simply and with minimal breaking change.

@Mad-Lynx
Copy link
Author

Mad-Lynx commented Feb 6, 2025

For ComplexCollection, the element type of the collection is string.

Even if you will define

public class ComplexCollection : StringCollection, IReadOnlyCollection<Rec>
{
    public void Add(Rec v) => Add(v.ToString());

    IEnumerator<Rec> IEnumerable<Rec>.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

You will not be able to use new Rec(42) in the construction.
So I'm guessing the assumption is, the collection expression will try to get the type of the collection, and will stick with string (as that's defined in the base type), and will skip the definition on ComplexCollection (allowing both types "to exists").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants