Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cston committed Apr 16, 2023
1 parent 4e68acc commit eafbd88
Show file tree
Hide file tree
Showing 21 changed files with 1,382 additions and 310 deletions.
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind
expr = BindIndexerDefaultArguments((BoundIndexerAccess)expr, valueKind, diagnostics);
break;

case BoundKind.UnconvertedObjectCreationExpression: // PROTOTYPE: What is the effect of commenting out this case? Add a corresponding test for collection literals (next case).
case BoundKind.UnconvertedObjectCreationExpression:
if (valueKind == BindValueKind.RValue)
{
return expr;
Expand Down
45 changes: 31 additions & 14 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ private BoundExpression ConvertCollectionLiteralExpression(
{
if (!arrayType.IsSZArray)
{
Error(diagnostics, ErrorCode.ERR_CollectionLiteralTargetTypeNotConstructible, syntax, targetType);
Error(diagnostics, ErrorCode.ERR_CollectionLiteralTargetTypeMultiDimensionalArray, syntax);
}
collectionLiteral = bindArrayOrSpan(syntax, targetType, spanConstructor: null, node.Initializers, arrayType.ElementType, diagnostics);
}
Expand All @@ -454,9 +454,12 @@ private BoundExpression ConvertCollectionLiteralExpression(
}
else
{
bool hasEnumerableInitializerType = collectionTypeImplementsIEnumerable(targetType, syntax, diagnostics);
bool hasEnumerableInitializerType = supportsCollectionInitializer(targetType, syntax, diagnostics);
if (!hasEnumerableInitializerType)
{
// PROTOTYPE: Why is this case here? We're being called with an explicit target type. Shouldn't
// the caller handle the conversion from natural type if the target type is not applicable?

// Target type is not constructible. Use collection literal natural type if available, instead.
if (node.Type is { } collectionType)
{
Expand All @@ -468,10 +471,10 @@ private BoundExpression ConvertCollectionLiteralExpression(
wasCompilerGenerated: node.WasCompilerGenerated,
diagnostics);
Debug.Assert(expr.Type is { });
// PROTOTYPE: Test various conversions, including conversions expected to fail.
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
conversion = Conversions.ClassifyImplicitConversionFromType(expr.Type, targetType, ref useSiteInfo);
// PROTOTYPE: Test.
conversion = isCast
? Conversions.ClassifyConversionFromType(expr.Type, targetType, isChecked: false, ref useSiteInfo, forCast: isCast)
: Conversions.ClassifyImplicitConversionFromType(expr.Type, targetType, ref useSiteInfo);
diagnostics.Add(syntax, useSiteInfo);
bool hasErrors = !conversion.IsValid;
if (hasErrors)
Expand All @@ -489,8 +492,10 @@ private BoundExpression ConvertCollectionLiteralExpression(
diagnostics,
hasErrors: hasErrors);
}

Error(diagnostics, ErrorCode.ERR_CollectionLiteralTargetTypeNotConstructible, syntax, targetType);
}

collectionLiteral = BindCollectionInitializerCollectionLiteral(node, targetType, wasTargetTyped: true, hasEnumerableInitializerType: hasEnumerableInitializerType, wasCompilerGenerated: wasCompilerGenerated, diagnostics);
}

Expand Down Expand Up @@ -546,15 +551,26 @@ BoundArrayOrSpanCollectionLiteralExpression bindArrayOrSpan(
return (MethodSymbol?)GetWellKnownTypeMember(spanMember, diagnostics, syntax: syntax)?.SymbolAsMember((NamedTypeSymbol)spanType);
}

bool collectionTypeImplementsIEnumerable(TypeSymbol targetType, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics)
bool supportsCollectionInitializer(TypeSymbol targetType, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics)
{
ImmutableArray<NamedTypeSymbol> allInterfaces;
switch (targetType.TypeKind)
{
case TypeKind.Class:
case TypeKind.Struct:
allInterfaces = targetType.AllInterfacesNoUseSiteDiagnostics;
break;
case TypeKind.TypeParameter:
allInterfaces = ((TypeParameterSymbol)targetType).AllEffectiveInterfacesNoUseSiteDiagnostics;
break;
default:
return false;
}

// This implementation differs from CollectionInitializerTypeImplementsIEnumerable().
// That method checks for an implicit conversion from IEnumerable to the collection type,
// but that would allow: Nullable<StructCollection> s = [];
var ienumerableType = GetSpecialType(SpecialType.System_Collections_IEnumerable, diagnostics, syntax);
var allInterfaces = targetType is TypeParameterSymbol typeParameter
? typeParameter.AllEffectiveInterfacesNoUseSiteDiagnostics
: targetType.AllInterfacesNoUseSiteDiagnostics;
return allInterfaces.Any(static (a, b) => a.Equals(b, TypeCompareKind.AllIgnoreOptions), ienumerableType);
}

Expand Down Expand Up @@ -589,7 +605,8 @@ private BoundCollectionInitializerCollectionLiteralExpression BindCollectionInit
bool wasTargetTyped,
bool hasEnumerableInitializerType,
bool wasCompilerGenerated,
BindingDiagnosticBag diagnostics)
BindingDiagnosticBag diagnostics,
bool hasErrors = false)
{
Debug.Assert(wasTargetTyped ||
targetType.IsErrorType() ||
Expand All @@ -601,9 +618,8 @@ private BoundCollectionInitializerCollectionLiteralExpression BindCollectionInit
if (targetType is NamedTypeSymbol namedType)
{
var analyzedArguments = AnalyzedArguments.GetInstance();
// PROTOTYPE: Use List<T>(int capacity) constructor when size is known, and when using natural type.
// What about when target-typed to List<T>? We should still use the int capacity constructor in that case.
// Review with LDM.
// PROTOTYPE: Should we use List<T>(int capacity) constructor when the size is known
// and using natural type? What about when target-typed to List<T>?
collectionCreation = BindClassCreationExpression(syntax, namedType.Name, syntax, namedType, analyzedArguments, diagnostics);
collectionCreation.WasCompilerGenerated = true;
analyzedArguments.Free();
Expand Down Expand Up @@ -641,7 +657,8 @@ private BoundCollectionInitializerCollectionLiteralExpression BindCollectionInit
wasTargetTyped: wasTargetTyped,
implicitReceiver,
builder.ToImmutableAndFree(),
targetType)
targetType,
hasErrors)
{ WasCompilerGenerated = wasCompilerGenerated };
}

Expand Down
71 changes: 43 additions & 28 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,6 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, BindingDi
collectionType = CreateErrorType();
hasErrors = true;
}
// PROTOTYPE: We're dropping hasErrors.
// PROTOTYPE: Do we need a Conversion here? For a BoundConvertedSwitchExpression,
// we're adding an Identity conversion when using the natural type rather than target type.
result = BindCollectionInitializerCollectionLiteral(
Expand All @@ -406,7 +405,8 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, BindingDi
wasTargetTyped: false,
hasEnumerableInitializerType: true,
wasCompilerGenerated: expr.WasCompilerGenerated,
diagnostics);
diagnostics,
hasErrors);
}
break;
default:
Expand Down Expand Up @@ -3440,7 +3440,6 @@ private BoundExpression BindImplicitArrayCreationExpression(ImplicitArrayCreatio
TypeSymbol bestType = BestTypeInferrer.InferBestType(boundInitializerExpressions, this.Conversions, ref useSiteInfo, out _);
diagnostics.Add(node, useSiteInfo);

// PROTOTYPE: When do we hit the void case? Add a corresponding test for collection literals.
if ((object)bestType == null || bestType.IsVoidType()) // Dev10 also reports ERR_ImplicitlyTypedArrayNoBestType for void.
{
Error(diagnostics, ErrorCode.ERR_ImplicitlyTypedArrayNoBestType, node);
Expand Down Expand Up @@ -4517,31 +4516,7 @@ private BoundExpression BindCollectionLiteralExpression(CollectionCreationExpres
}

var initializers = builder.ToImmutableAndFree();
TypeSymbol? collectionType = null;

// PROTOTYPE: Confirm with LDM that we use the element expressions rather than
// the element types to infer best common type.
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
var bestType = BestTypeInferrer.InferBestType(initializers, Conversions, ref useSiteInfo, out _);
if (bestType is { })
{
if (bestType.IsRestrictedType(ignoreSpanLikeTypes: true))
{
// PROTOTYPE: Use error specific to collection literals.
Error(diagnostics, ErrorCode.ERR_ArrayElementCantBeRefAny, syntax, bestType);
}
else
{
// PROTOTYPE: When using the natural type, verify each element can be converted to the
// common type, even if there is an Add() overload for the unconverted value.

// PROTOTYPE: Ensure use-site error is reported when List<T> is missing. See Binder.GetWellKnownType().
// Clone test EnumType_02() and remove type List<T> for instance.
collectionType = GetWellKnownType(WellKnownType.System_Collections_Generic_List_T, ref useSiteInfo).Construct(bestType);
}
}

diagnostics.Add(syntax, useSiteInfo);
TypeSymbol? collectionType = InferCollectionLiteralType(syntax, initializers, diagnostics);
return new BoundUnconvertedCollectionLiteralExpression(syntax, initializers, this, type: collectionType);

BoundExpression bindElement(CollectionElementSyntax syntax, BindingDiagnosticBag diagnostics)
Expand All @@ -4567,6 +4542,46 @@ BoundExpression bindElement(CollectionElementSyntax syntax, BindingDiagnosticBag
}
}
}

// This is similar to the approach used for switch expressions in SwitchExpressionBinder.InferResultType
// which finds a best common type and checks all arms can be converted to that type.
// With switch expressions though, target typing is used when there is no natural type,
// while with collection literals, the natural type is used when there is no target type.
private TypeSymbol? InferCollectionLiteralType(CollectionCreationExpressionSyntax syntax, ImmutableArray<BoundExpression> initializers, BindingDiagnosticBag diagnostics)
{
TypeSymbol? collectionType = null;
var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);

var bestType = BestTypeInferrer.InferBestType(initializers, Conversions, ref useSiteInfo, out _);
if (bestType is { } && !bestType.IsVoidType())
{
if (bestType.IsRestrictedType(ignoreSpanLikeTypes: true))
{
// PROTOTYPE: Use error specific to collection literals.
Error(diagnostics, ErrorCode.ERR_ArrayElementCantBeRefAny, syntax, bestType);
}
else
{
// Check that each element (even those without a type) can be converted to the best type.
foreach (var initializer in initializers)
{
if (!Conversions.ClassifyImplicitConversionFromExpression(initializer, bestType, ref useSiteInfo).Exists)
{
bestType = null;
break;
}
}

if (bestType is { })
{
collectionType = GetWellKnownType(WellKnownType.System_Collections_Generic_List_T, ref useSiteInfo).Construct(bestType);
}
}
}

diagnostics.Add(syntax, useSiteInfo);
return collectionType;
}
#nullable disable

private BoundExpression BindDelegateCreationExpression(ObjectCreationExpressionSyntax node, NamedTypeSymbol type, BindingDiagnosticBag diagnostics)
Expand Down
5 changes: 4 additions & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6781,7 +6781,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<value>collection literals</value>
</data>
<data name="ERR_CollectionLiteralTargetTypeNotConstructible" xml:space="preserve">
<value>Cannot initialize type '{0}' with a collection literal because the type is not constructible.</value>
<value>Cannot initialize type '{0}' with the collection literal because the type is not constructible and no best type was found for the collection literal.</value>
</data>
<data name="ERR_ExpressionTreeContainsCollectionLiteral" xml:space="preserve">
<value>An expression tree may not contain a collection literal.</value>
Expand All @@ -6792,6 +6792,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_ImplicitlyTypedCollectionLiteralNoBestType" xml:space="preserve">
<value>No best type found for implicitly-typed collection literal.</value>
</data>
<data name="ERR_CollectionLiteralTargetTypeMultiDimensionalArray" xml:space="preserve">
<value>A collection literal cannot be target typed to a multidimensional array.</value>
</data>
<data name="ERR_EqualityContractRequiresGetter" xml:space="preserve">
<value>Record equality contract property '{0}' must have a get accessor.</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 @@ -2191,6 +2191,7 @@ internal enum ErrorCode
ERR_ExpressionTreeContainsCollectionLiteral = 9501,
ERR_CollectionLiteralElementNotImplemented = 9502, // PROTOTYPE: Temporary error until feature has been implemented.
ERR_ImplicitlyTypedCollectionLiteralNoBestType = 9503,
ERR_CollectionLiteralTargetTypeMultiDimensionalArray = 9504,

#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 @@ -2311,6 +2311,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_ExpressionTreeContainsCollectionLiteral:
case ErrorCode.ERR_CollectionLiteralElementNotImplemented:
case ErrorCode.ERR_ImplicitlyTypedCollectionLiteralNoBestType:
case ErrorCode.ERR_CollectionLiteralTargetTypeMultiDimensionalArray:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ internal sealed partial class LocalRewriter

foreach (var initializer in initializers)
{
var rewrittenInitializer = initializer is BoundCollectionElementInitializer element
? MakeCollectionInitializer(temp, element)
: throw ExceptionUtilities.UnexpectedValue(initializer.Kind);
var rewrittenInitializer = initializer switch
{
BoundCollectionElementInitializer collectionElement => MakeCollectionInitializer(temp, collectionElement),
BoundDynamicCollectionElementInitializer dynamicElement => MakeDynamicCollectionInitializer(temp, dynamicElement),
_ => throw ExceptionUtilities.UnexpectedValue(initializer)
};
// PROTOTYPE: Test with null. For instance, MakeCollectionInitializer()
// returns null when the Add() call is omitted.
if (rewrittenInitializer != null)
{
sideEffects.Add(rewrittenInitializer);
Expand Down
9 changes: 7 additions & 2 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.

9 changes: 7 additions & 2 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.

Loading

0 comments on commit eafbd88

Please sign in to comment.