csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

Collection expression loophole: ability to invoke .Add() method with the parameter type not matching the collection expression element type

Open controlflow opened this issue 2 years ago • 9 comments

Version Used: main branch

Steps to Reproduce:

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

BadCandidateForCollectionExpression xs = [123]; // no .Add(int)
BadCandidateForCollectionExpression ys = ["abc"]; // not int element type
BadCandidateForCollectionExpression zs = [default]; // ????

class BadCandidateForCollectionExpression : IEnumerable<int>
{
  public void Add(string s) { }

  IEnumerator<int> IEnumerable<int>.GetEnumerator() { yield break; }
  IEnumerator IEnumerable.GetEnumerator() { yield break; }
}

Expected Behavior:

Impossible to fill the collection expression, no Add method can accept the value of int type - the element type inferred as a collection expression element type.

Actual Behavior:

If the expression is both convertible to inferred element type and can be accepted by some non-int Add method overload - it compiles successfully. As the result you have a really weird language context - you can only pass expressions that are implicitly convertible to int, but the invoked method accepts string value.

I guess this is the poorly written check in frontend, I guess instead of checking the element expression to be convertible to int you probably must check the parameter type of invoked .Add() method to have type convertible to int.

controlflow avatar Oct 29 '23 22:10 controlflow

@cston @333fred to see if this is by design based on our last meetings

I don't imagine this is an interesting case to support given that it doesn't behave like any normal collection does.

CyrusNajmabadi avatar Oct 29 '23 22:10 CyrusNajmabadi

This doesn't seem like it should compile. The type of default should be inferred to be the element type.

333fred avatar Oct 30 '23 05:10 333fred

I believe this is by-design from current spec. There exists a conversion from default to the iteration type int and default can also be used to invoke .Add. Those two checks are independent (the types don't have to match).

jcouv avatar Oct 30 '23 18:10 jcouv

feels like a spec bug :)

RikkiGibson avatar Oct 31 '23 18:10 RikkiGibson

Moving to csharplang to close the spec bug.

jaredpar avatar Nov 28 '23 05:11 jaredpar

This should not be supported unless we can find a reasonable use case. Otherwise, It shouldn't be supported for collection expressions.

Ai-N3rd avatar Mar 04 '24 16:03 Ai-N3rd

A behavior I found a bit amusing here is that if you change the parameter into a float, and you try to pass a float to that method it will complain that it will only allow an int, not a float! If you give it an int however, it will silently pass a float instead.

GeirGrusom avatar Mar 22 '24 15:03 GeirGrusom

There should probably be a check at compile time. It would simply check if the .Add() method has the same type as the IEnumerable. If not, it can't be used in collections expressions.

Ai-N3rd avatar Mar 22 '24 17:03 Ai-N3rd

There should probably be a check at compile time.

Perhaps a diagnostic could be reported if the element as converted for the Add call is not implicitly convertible to the collection iteration type.

BadCandidateForCollectionExpression zs = [default]; // warning: converting 'default' to 'string' for Add(string)

cston avatar Mar 22 '24 17:03 cston