Skip to content

Commit

Permalink
Collection expressions: report specific error for spread with no GetE…
Browse files Browse the repository at this point in the history
…numerator() method (#70717)
  • Loading branch information
cston authored Nov 9, 2023
1 parent 5e2cf6d commit eba055a
Show file tree
Hide file tree
Showing 20 changed files with 145 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ internal bool TryGetCollectionIterationType(ExpressionSyntax syntax, TypeSymbol
syntax,
ref collectionExpr,
isAsync: false,
isSpread: false,
BindingDiagnosticBag.Discarded,
out iterationType,
builder: out _);
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4758,7 +4758,7 @@ BoundNode bindSpreadElement(SpreadElementSyntax syntax, BindingDiagnosticBag dia
{
var expression = BindRValueWithoutTargetType(syntax.Expression, diagnostics);
ForEachEnumeratorInfo.Builder builder;
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(syntax, syntax.Expression, ref expression, isAsync: false, diagnostics, inferredType: out _, out builder) ||
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(syntax, syntax.Expression, ref expression, isAsync: false, isSpread: true, diagnostics, inferredType: out _, out builder) ||
builder.IsIncomplete;
if (hasErrors)
{
Expand Down
33 changes: 25 additions & 8 deletions src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ internal override BoundStatement BindForEachDeconstruction(BindingDiagnosticBag
BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics);

TypeWithAnnotations inferredType;
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, IsAsync, diagnostics, out inferredType, builder: out _);
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out inferredType, builder: out _);

ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable;

Expand Down Expand Up @@ -225,7 +225,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno

ForEachEnumeratorInfo.Builder builder;
TypeWithAnnotations inferredType;
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, IsAsync, diagnostics, out inferredType, out builder);
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(_syntax, _syntax.Expression, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out inferredType, out builder);

// These occur when special types are missing or malformed, or the patterns are incompletely implemented.
hasErrors |= builder.IsIncomplete;
Expand Down Expand Up @@ -610,7 +610,7 @@ internal TypeWithAnnotations InferCollectionElementType(BindingDiagnosticBag dia
// Use the right binder to avoid seeing iteration variable
BoundExpression collectionExpr = this.GetBinder(collectionSyntax).BindValue(collectionSyntax, diagnostics, BindValueKind.RValue);

GetEnumeratorInfoAndInferCollectionElementType(_syntax, collectionSyntax, ref collectionExpr, IsAsync, diagnostics, out TypeWithAnnotations inferredType, builder: out _);
GetEnumeratorInfoAndInferCollectionElementType(_syntax, collectionSyntax, ref collectionExpr, isAsync: IsAsync, isSpread: false, diagnostics, out TypeWithAnnotations inferredType, builder: out _);
return inferredType;
}
}
Expand Down Expand Up @@ -661,11 +661,14 @@ internal bool GetEnumeratorInfoAndInferCollectionElementType(
ExpressionSyntax collectionSyntax,
ref BoundExpression collectionExpr,
bool isAsync,
bool isSpread,
BindingDiagnosticBag diagnostics,
out TypeWithAnnotations inferredType,
out ForEachEnumeratorInfo.Builder builder)
{
bool gotInfo = GetEnumeratorInfo(syntax, collectionSyntax, ref collectionExpr, isAsync, diagnostics, out builder);
Debug.Assert(!isAsync || !isSpread);

bool gotInfo = GetEnumeratorInfo(syntax, collectionSyntax, ref collectionExpr, isAsync, isSpread, diagnostics, out builder);

if (!gotInfo)
{
Expand Down Expand Up @@ -749,8 +752,17 @@ private BoundExpression UnwrapCollectionExpressionIfNullable(BoundExpression col
/// <param name="collectionExpr">The expression over which to iterate.</param>
/// <param name="diagnostics">Populated with binding diagnostics.</param>
/// <returns>Partially populated (all but conversions) or null if there was an error.</returns>
private bool GetEnumeratorInfo(SyntaxNode syntax, ExpressionSyntax collectionSyntax, ref BoundExpression collectionExpr, bool isAsync, BindingDiagnosticBag diagnostics, out ForEachEnumeratorInfo.Builder builder)
private bool GetEnumeratorInfo(
SyntaxNode syntax,
ExpressionSyntax collectionSyntax,
ref BoundExpression collectionExpr,
bool isAsync,
bool isSpread,
BindingDiagnosticBag diagnostics,
out ForEachEnumeratorInfo.Builder builder)
{
Debug.Assert(!isAsync || !isSpread);

BoundExpression originalCollectionExpr = collectionExpr;

EnumeratorResult found = GetEnumeratorInfoCore(syntax, collectionSyntax, ref collectionExpr, isAsync, diagnostics, out builder);
Expand All @@ -776,9 +788,14 @@ private bool GetEnumeratorInfo(SyntaxNode syntax, ExpressionSyntax collectionSyn
// Retry with a different assumption about whether the foreach is async
bool wrongAsync = GetEnumeratorInfoCore(syntax, collectionSyntax, ref originalCollectionExpr, !isAsync, BindingDiagnosticBag.Discarded, builder: out _) == EnumeratorResult.Succeeded;

var errorCode = wrongAsync
? (isAsync ? ErrorCode.ERR_AwaitForEachMissingMemberWrongAsync : ErrorCode.ERR_ForEachMissingMemberWrongAsync)
: (isAsync ? ErrorCode.ERR_AwaitForEachMissingMember : ErrorCode.ERR_ForEachMissingMember);
ErrorCode errorCode = (wrongAsync, isAsync, isSpread) switch
{
(true, true, _) => ErrorCode.ERR_AwaitForEachMissingMemberWrongAsync,
(true, false, _) => ErrorCode.ERR_ForEachMissingMemberWrongAsync,
(false, true, _) => ErrorCode.ERR_AwaitForEachMissingMember,
(false, false, true) => ErrorCode.ERR_SpreadMissingMember,
(false, false, false) => ErrorCode.ERR_ForEachMissingMember,
};

diagnostics.Add(errorCode, collectionSyntax.Location,
collectionExprType, isAsync ? WellKnownMemberNames.GetAsyncEnumeratorMethodName : WellKnownMemberNames.GetEnumeratorMethodName);
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2758,6 +2758,9 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep
<data name="ERR_AwaitForEachMissingMemberWrongAsync" xml:space="preserve">
<value>Asynchronous foreach statement cannot operate on variables of type '{0}' because '{0}' does not contain a public instance or extension definition for '{1}'. Did you mean 'foreach' rather than 'await foreach'?</value>
</data>
<data name="ERR_SpreadMissingMember" xml:space="preserve">
<value>Spread operator '..' cannot operate on variables of type '{0}' because '{0}' does not contain a public instance or extension definition for '{1}'</value>
</data>
<data name="ERR_PossibleAsyncIteratorWithoutYield" xml:space="preserve">
<value>The body of an async-iterator method must contain a 'yield' statement.</value>
</data>
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2280,6 +2280,7 @@ internal enum ErrorCode
ERR_CollectionExpressionImmutableArray = 9210,

ERR_InvalidExperimentalDiagID = 9211,
ERR_SpreadMissingMember = 9212,

#endregion

Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate:
case ErrorCode.ERR_CollectionExpressionImmutableArray:
case ErrorCode.ERR_InvalidExperimentalDiagID:
case ErrorCode.ERR_SpreadMissingMember:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit eba055a

Please sign in to comment.