From 4a4ce2d717c4b4e832b472053ac2df1db65c3767 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:30:07 -0800 Subject: [PATCH 01/16] Dictionary expressions: initial binding and lowering support --- .../Portable/Binder/Binder_Conversions.cs | 118 ++++++++++++-- .../Portable/Binder/Binder_Expressions.cs | 51 ++++-- .../CollectionExpressionTypeKind.cs | 1 + .../Semantics/Conversions/Conversion.cs | 5 + .../Semantics/Conversions/ConversionKind.cs | 1 + .../Conversions/ConversionKindExtensions.cs | 1 + .../Semantics/Conversions/Conversions.cs | 96 ++++++++++-- .../Semantics/Conversions/ConversionsBase.cs | 36 ++++- .../CSharp/Portable/BoundTree/BoundNodes.xml | 5 + .../Portable/FlowAnalysis/AbstractFlowPass.cs | 7 + .../Portable/FlowAnalysis/NullableWalker.cs | 4 + .../Generated/BoundNodes.xml.Generated.cs | 55 +++++++ .../LocalRewriter_CollectionExpression.cs | 127 +++++++++++++++ .../CSharp/Portable/PublicAPI.Unshipped.txt | 1 + .../Semantics/CollectionExpressionTests.cs | 5 +- .../Semantics/DictionaryExpressionTests.cs | 145 ++++++++++++++++++ .../Core/Portable/WellKnownMember.cs | 5 + .../Core/Portable/WellKnownMembers.cs | 34 ++++ src/Compilers/Core/Portable/WellKnownTypes.cs | 6 + 19 files changed, 661 insertions(+), 42 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 7df03440b692e..3fcc8bc512135 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -400,6 +400,34 @@ void ensureAllUnderlyingConversionsChecked(SyntaxNode syntax, BoundExpression so Debug.Assert(conversion.UnderlyingConversions.IsDefault); conversion.MarkUnderlyingConversionsChecked(); } + else if (conversion.IsKeyValuePair) + { + if (!ConversionsBase.IsKeyValuePairType(Compilation, source.Type, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var sourceKeyType, out var sourceValueType) || + !ConversionsBase.IsKeyValuePairType(Compilation, destination, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var destinationKeyType, out var destinationValueType)) + { + throw ExceptionUtilities.UnexpectedValue(source.Type); + } + // PROTOTYPE: Test that we're reporting errors from this code path. + _ = CreateConversion( + syntax, + new BoundValuePlaceholder(source.Syntax, sourceKeyType.Type), + conversion.UnderlyingConversions[0], + isCast: false, + conversionGroupOpt: null, + wasCompilerGenerated: true, + destinationKeyType.Type, + diagnostics); + _ = CreateConversion( + syntax, + new BoundValuePlaceholder(source.Syntax, sourceValueType.Type), + conversion.UnderlyingConversions[1], + isCast: false, + conversionGroupOpt: null, + wasCompilerGenerated: true, + destinationValueType.Type, + diagnostics); + conversion.MarkUnderlyingConversionsChecked(); + } conversion.AssertUnderlyingConversionsCheckedRecursive(); } @@ -909,20 +937,26 @@ private BoundCollectionExpression ConvertCollectionExpression( var collectionInitializerAddMethodBinder = new CollectionInitializerAddMethodBinder(syntax, targetType, this); foreach (var element in elements) { - BoundNode convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ? - (BoundNode)BindCollectionExpressionSpreadElementAddMethod( + BoundNode convertedElement = element switch + { + BoundCollectionExpressionSpreadElement spreadElement => (BoundNode)BindCollectionExpressionSpreadElementAddMethod( (SpreadElementSyntax)spreadElement.Syntax, spreadElement, collectionInitializerAddMethodBinder, implicitReceiver, - diagnostics) : - BindCollectionInitializerElementAddMethod( + diagnostics), + BoundKeyValuePairElement keyValuePairElement => BindKeyValuePairAddMethod( + keyValuePairElement, + implicitReceiver, + diagnostics), + _ => BindCollectionInitializerElementAddMethod( element.Syntax, ImmutableArray.Create((BoundExpression)element), hasEnumerableInitializerType: true, collectionInitializerAddMethodBinder, diagnostics, - implicitReceiver); + implicitReceiver) + }; builder.Add(convertedElement); } } @@ -950,20 +984,30 @@ private BoundCollectionExpression ConvertCollectionExpression( { var element = elements[i]; var elementConversion = elementConversions[i]; - var convertedElement = element is BoundCollectionExpressionSpreadElement spreadElement ? - bindSpreadElement( - spreadElement, - elementType, - elementConversion, - diagnostics) : - CreateConversion( + var convertedElement = element switch + { + BoundCollectionExpressionSpreadElement spreadElement => + bindSpreadElement( + spreadElement, + elementType, + elementConversion, + diagnostics), + BoundKeyValuePairElement keyValuePairElement => + bindKeyValuePairElement( + keyValuePairElement, + elementType, + elementConversion, + diagnostics), + _ => CreateConversion( element.Syntax, (BoundExpression)element, elementConversion, isCast: false, conversionGroupOpt: null, destination: elementType, - diagnostics); + diagnostics) + }; + builder.Add(convertedElement!); } conversion.MarkUnderlyingConversionsChecked(); @@ -1009,6 +1053,32 @@ BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, Type iteratorBody: new BoundExpressionStatement(expressionSyntax, convertElement) { WasCompilerGenerated = true }, lengthOrCount: element.LengthOrCount); } + + BoundNode bindKeyValuePairElement(BoundKeyValuePairElement element, TypeSymbol elementType, Conversion elementConversion, BindingDiagnosticBag diagnostics) + { + Debug.Assert(ReferenceEquals(elementType.OriginalDefinition, Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV))); + Debug.Assert(elementConversion is { Kind: ConversionKind.KeyValuePair, UnderlyingConversions.Length: 2 }); + + var typeArguments = ((NamedTypeSymbol)elementType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + var underlyingConversions = elementConversion.UnderlyingConversions; + var keyConversion = CreateConversion( + element.Key.Syntax, + element.Key, + underlyingConversions[0], + isCast: false, + conversionGroupOpt: null, + destination: typeArguments[0].Type, + diagnostics); + var valueConversion = CreateConversion( + element.Value.Syntax, + element.Value, + underlyingConversions[1], + isCast: false, + conversionGroupOpt: null, + destination: typeArguments[1].Type, + diagnostics); + return element.Update(keyConversion, valueConversion); + } } private bool HasCollectionInitializerTypeInProgress(SyntaxNode syntax, TypeSymbol targetType) @@ -1132,6 +1202,9 @@ internal bool HasCollectionExpressionApplicableConstructor(SyntaxNode syntax, Ty var analyzedArguments = AnalyzedArguments.GetInstance(); var binder = new ParamsCollectionTypeInProgressBinder(namedType, this); + // PROTOTYPE: Test with constructor that takes a comparer as well. + // PROTOTYPE: Test the comparer case for non-dictionary collections as well. + // PROTOTYPE: Test the comparer case with/without an explicit comparer in the collection expression. bool overloadResolutionSucceeded = binder.TryPerformConstructorOverloadResolution( namedType, analyzedArguments, @@ -1235,13 +1308,29 @@ private bool HasParamsCollectionTypeInProgress(NamedTypeSymbol toCheck, return false; } + // PROTOTYPE: This is a simple implementation. What else is needed? + internal bool HasCollectionExpressionApplicableIndexer(SyntaxNode syntax, TypeSymbol targetType, TypeSymbol elementType, out ImmutableArray indexers, BindingDiagnosticBag diagnostics) + { + var lookupResult = LookupResult.GetInstance(); + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + // PROTOTYPE: Test with missing indexer, multiple indexers, readonly/writeonly indexer, different member kind, etc. + // PROTOTYPE: Test with indexer that doesn't match elementType key and value types. + LookupMembersWithFallback(lookupResult, targetType, WellKnownMemberNames.Indexer, arity: 0, ref useSiteInfo); + diagnostics.Add(syntax, useSiteInfo); // PROTOTYPE: Test use-site diagnostics. + indexers = lookupResult.SingleSymbolOrDefault is PropertySymbol property ? + ImmutableArray.Create(property) : + ImmutableArray.Empty; + lookupResult.Free(); + return indexers.Length > 0; + } + internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, TypeSymbol targetType, out ImmutableArray addMethods, BindingDiagnosticBag diagnostics) { Debug.Assert(!targetType.IsDynamic()); NamedTypeSymbol? namedType = targetType as NamedTypeSymbol; - if (namedType is not null && HasParamsCollectionTypeInProgress(namedType, out _, out _)) + if (namedType is not null && HasParamsCollectionTypeInProgress(namedType, out _, out _)) // PROTOTYPE: Test cycles with params for indexers with dictionary types and non-dictionary types. { // We are in a cycle. Optimistically assume we have the right Add to break the cycle addMethods = []; @@ -1810,6 +1899,7 @@ internal void GenerateImplicitConversionErrorForCollectionExpression( } else { + // PROTOTYPE: Handle k:v. Conversion elementConversion = Conversions.ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo); if (!elementConversion.Exists) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 6763a986a7f42..23ce4b7aa68bd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5199,18 +5199,6 @@ private BoundExpression BindCollectionExpression(CollectionExpressionSyntax synt MessageID.IDS_FeatureCollectionExpressions.CheckFeatureAvailability(diagnostics, syntax, syntax.OpenBracketToken.GetLocation()); - foreach (var element in syntax.Elements) - { - if (element is KeyValuePairElementSyntax keyValuePairElement) - { - MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax, keyValuePairElement.ColonToken.GetLocation()); - - // PROTOTYPE: Error for now. Flesh this out when we do the binding for kvp elements. - Error(diagnostics, ErrorCode.ERR_SyntaxError, keyValuePairElement.ColonToken, ","); - return new BoundBadExpression(syntax, LookupResultKind.Empty, symbols: [], childBoundNodes: [], CreateErrorType()); - } - } - var builder = ArrayBuilder.GetInstance(syntax.Elements.Count); foreach (var element in syntax.Elements) { @@ -5224,6 +5212,7 @@ static BoundNode bindElement(CollectionElementSyntax syntax, BindingDiagnosticBa { ExpressionElementSyntax { Expression: CollectionExpressionSyntax nestedCollectionExpression } => @this.BindCollectionExpression(nestedCollectionExpression, diagnostics, nestingLevel + 1), ExpressionElementSyntax expressionElementSyntax => @this.BindValue(expressionElementSyntax.Expression, diagnostics, BindValueKind.RValue), + KeyValuePairElementSyntax kvpElementSyntax => @this.BindKeyValuePair(kvpElementSyntax, diagnostics), SpreadElementSyntax spreadElementSyntax => bindSpreadElement(spreadElementSyntax, diagnostics, @this), _ => throw ExceptionUtilities.UnexpectedValue(syntax.Kind()) }; @@ -5285,6 +5274,16 @@ static BoundNode bindSpreadElement(SpreadElementSyntax syntax, BindingDiagnostic hasErrors: false); } } + + private BoundNode BindKeyValuePair(KeyValuePairElementSyntax syntax, BindingDiagnosticBag diagnostics) + { + MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax, syntax.ColonToken.GetLocation()); + // PROTOTYPE: Test when key/value are not r-values. + // PROTOTYPE: Test when key/value do not have types. + var key = BindValue(syntax.KeyExpression, diagnostics, BindValueKind.RValue); + var value = BindValue(syntax.ValueExpression, diagnostics, BindValueKind.RValue); + return new BoundKeyValuePairElement(syntax, key, value); + } #nullable disable private BoundExpression BindDelegateCreationExpression(ObjectCreationExpressionSyntax node, NamedTypeSymbol type, BindingDiagnosticBag diagnostics) @@ -6519,6 +6518,33 @@ static void copyRelevantAddMethodDiagnostics(BindingDiagnosticBag source, Bindin } #nullable enable + // PROTOTYPE: What is the shape of the Add() method for a k:v? + // Does it take two arguments? Or a KeyValuePair<,>? And if a KeyValuePair<,>, + // how do we bind to it so the K and V conversions can be calculated? + private BoundExpression BindKeyValuePairAddMethod( + BoundKeyValuePairElement element, + BoundObjectOrCollectionValuePlaceholder implicitReceiver, + BindingDiagnosticBag diagnostics) + { + // PROTOTYPE: Should use Add rather than indexer for collections that do not have indexers. + // PROTOTYPE: Test with get-only/set-only indexer, inaccessible accessor, etc. + // PROTOTYPE: Test with ref indexer. + var analyzedArguments = AnalyzedArguments.GetInstance(); + analyzedArguments.Arguments.Add(element.Key); + var left = BindIndexerAccess(element.Syntax, implicitReceiver, analyzedArguments, diagnostics); + analyzedArguments.Free(); + if (left is BoundIndexerAccess indexerAccess) + { + left = indexerAccess.Update(AccessorKind.Set); + } + return BindAssignment( + element.Syntax, + left, + element.Value, + isRef: false, + diagnostics); + } + private BoundCollectionExpressionSpreadElement BindCollectionExpressionSpreadElementAddMethod( SpreadElementSyntax syntax, BoundCollectionExpressionSpreadElement element, @@ -6541,6 +6567,7 @@ private BoundCollectionExpressionSpreadElement BindCollectionExpressionSpreadEle Debug.Assert(enumeratorInfo.ElementType is { }); // ElementType is set always, even for IEnumerable. var addElementPlaceholder = new BoundValuePlaceholder(syntax, enumeratorInfo.ElementType); + // PROTOTYPE: Test when binding to a dictionary type. var addMethodInvocation = BindCollectionInitializerElementAddMethod( syntax.Expression, ImmutableArray.Create((BoundExpression)addElementPlaceholder), diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/CollectionExpressionTypeKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/CollectionExpressionTypeKind.cs index 40d70499e1f38..5f8c5346d171e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/CollectionExpressionTypeKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/CollectionExpressionTypeKind.cs @@ -13,5 +13,6 @@ internal enum CollectionExpressionTypeKind CollectionBuilder, ImplementsIEnumerable, ArrayInterface, + DictionaryInterface, } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index 731dcf784e037..dc05b218fd9a4 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -714,6 +714,11 @@ public bool IsObjectCreation /// public bool IsCollectionExpression => Kind == ConversionKind.CollectionExpression; + /// + /// Returns true if the conversion is an implicit conversion of a key:value pair in a collection expression. + /// + public bool IsKeyValuePair => Kind == ConversionKind.KeyValuePair; // PROTOTYPE: Add key-value pair conversion to spec. + /// /// Returns true if the conversion is an implicit switch expression conversion. /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs index e60ed4d88b65f..b8afd0fa4d760 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs @@ -64,6 +64,7 @@ internal enum ConversionKind : byte DefaultLiteral, // a conversion from a `default` literal to any type ObjectCreation, // a conversion from a `new()` expression to any type CollectionExpression, // a conversion from a collection expression to any type + KeyValuePair, // a conversion from a k:v to a KeyValuePair<,> InterpolatedStringHandler, // A conversion from an interpolated string literal to a type attributed with InterpolatedStringBuilderAttribute diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs index b25b4bef8aaa6..7f9a027de2e3f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs @@ -54,6 +54,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind) case InlineArray: case CollectionExpression: case ImplicitSpan: + case KeyValuePair: return true; case ExplicitNumeric: diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index abb608ffe3b20..d3bf97007321d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -193,17 +193,46 @@ protected override Conversion GetCollectionExpressionConversion( return Conversion.NoConversion; } - if (elements.Length > 0 && + if ((!ReferenceEquals(elementType.OriginalDefinition, Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV)) || + !_binder.HasCollectionExpressionApplicableIndexer(syntax, targetType, elementType, out _, BindingDiagnosticBag.Discarded)) && + elements.Length > 0 && !_binder.HasCollectionExpressionApplicableAddMethod(syntax, targetType, addMethods: out _, BindingDiagnosticBag.Discarded)) { return Conversion.NoConversion; } } + TypeWithAnnotations keyType; + TypeWithAnnotations valueType; + bool usesKeyValuePairs = IsKeyValuePairType(Compilation, elementType, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out keyType, out valueType); + var builder = ArrayBuilder.GetInstance(elements.Length); foreach (var element in elements) { - Conversion elementConversion = convertElement(element, elementType, ref useSiteInfo); + Conversion elementConversion; + if (usesKeyValuePairs) + { + // PROTOTYPE: This change in behavior should only be enabled in C#14. Test both + // dictionaries and other collections of KeyValuePair<,> with earlier language version. + Debug.Assert(keyType.Type is { }); + Debug.Assert(valueType.Type is { }); + elementConversion = element switch + { + BoundCollectionExpressionSpreadElement spreadElement => GetSpreadElementAsKeyValuePairConversion(spreadElement, keyType.Type, valueType.Type, ref useSiteInfo), + BoundKeyValuePairElement keyValuePairElement => GetKeyValuePairElementConversion(keyValuePairElement, keyType.Type, valueType.Type, ref useSiteInfo), + _ => GetExpressionElementAsKeyValuePairConversion((BoundExpression)element, keyType.Type, valueType.Type, ref useSiteInfo), + }; + } + else + { + elementConversion = element switch + { + BoundCollectionExpressionSpreadElement spreadElement => GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo), + BoundKeyValuePairElement keyValuePairElement => Conversion.NoConversion, // PROTOTYPE: Test this case. + _ => ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo), + }; + } + if (!elementConversion.Exists) { builder.Free(); @@ -214,15 +243,6 @@ protected override Conversion GetCollectionExpressionConversion( } return Conversion.CreateCollectionExpressionConversion(collectionTypeKind, elementType, constructor, isExpanded, builder.ToImmutableAndFree()); - - Conversion convertElement(BoundNode element, TypeSymbol elementType, ref CompoundUseSiteInfo useSiteInfo) - { - return element switch - { - BoundCollectionExpressionSpreadElement spreadElement => GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo), - _ => ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo), - }; - } } internal Conversion GetCollectionExpressionSpreadElementConversion( @@ -235,11 +255,65 @@ internal Conversion GetCollectionExpressionSpreadElementConversion( { return Conversion.NoConversion; } + // PROTOTYPE: This should be conversion from type rather than conversion + // from expression. The difference is in handling of dynamic. return ClassifyImplicitConversionFromExpression( new BoundValuePlaceholder(element.Syntax, enumeratorInfo.ElementType), targetType, ref useSiteInfo); } + + private Conversion GetExpressionElementAsKeyValuePairConversion( + BoundExpression element, + TypeSymbol keyType, + TypeSymbol valueType, + ref CompoundUseSiteInfo useSiteInfo) + { + // PROTOTYPE: Test expression element with no type. + if (IsKeyValuePairType(Compilation, element.Type, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var elementKeyType, out var elementValueType)) + { + var key = ClassifyImplicitConversionFromType(elementKeyType.Type, keyType, ref useSiteInfo); + var value = ClassifyImplicitConversionFromType(elementValueType.Type, valueType, ref useSiteInfo); + if (key.Exists && value.Exists) + { + return new Conversion(ConversionKind.KeyValuePair, [key, value]); + } + } + return Conversion.NoConversion; + } + + private Conversion GetSpreadElementAsKeyValuePairConversion( + BoundCollectionExpressionSpreadElement element, + TypeSymbol keyType, + TypeSymbol valueType, + ref CompoundUseSiteInfo useSiteInfo) + { + var enumeratorInfo = element.EnumeratorInfoOpt; + if (enumeratorInfo is null) + { + return Conversion.NoConversion; + } + return GetExpressionElementAsKeyValuePairConversion( + new BoundValuePlaceholder(element.Syntax, enumeratorInfo.ElementType), + keyType, + valueType, + ref useSiteInfo); + } + + private Conversion GetKeyValuePairElementConversion( + BoundKeyValuePairElement element, + TypeSymbol keyType, + TypeSymbol valueType, + ref CompoundUseSiteInfo useSiteInfo) + { + var key = ClassifyImplicitConversionFromExpression(element.Key, keyType, ref useSiteInfo); + var value = ClassifyImplicitConversionFromExpression(element.Value, valueType, ref useSiteInfo); + if (key.Exists && value.Exists) + { + return new Conversion(ConversionKind.KeyValuePair, [key, value]); + } + return Conversion.NoConversion; + } #nullable disable /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 05b8dba5dc1cc..d927da7e989ac 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -1693,6 +1693,10 @@ internal static CollectionExpressionTypeKind GetCollectionExpressionTypeKind(CSh { return CollectionExpressionTypeKind.ArrayInterface; } + else if (isDictionaryInterface(compilation, destination, out elementType)) + { + return CollectionExpressionTypeKind.DictionaryInterface; + } elementType = default; return CollectionExpressionTypeKind.None; @@ -1703,9 +1707,24 @@ static bool implementsSpecialInterface(CSharpCompilation compilation, TypeSymbol var specialType = compilation.GetSpecialType(specialInterface); return allInterfaces.Any(static (a, b) => ReferenceEquals(a.OriginalDefinition, b), specialType); } + + static bool isDictionaryInterface(CSharpCompilation compilation, TypeSymbol targetType, out TypeWithAnnotations elementType) + { + // PROTOTYPE: Support IReadOnlyDictionary as well. + if (targetType is NamedTypeSymbol { Arity: 2 } namedType && + ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IDictionary_KV))) + { + // PROTOTYPE: Test with missing KeyValuePair<,>. + elementType = TypeWithAnnotations.Create(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV). + Construct(namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)); + return true; + } + elementType = default; + return false; + } } - internal static bool IsSpanOrListType(CSharpCompilation compilation, TypeSymbol targetType, WellKnownType spanType, [NotNullWhen(true)] out TypeWithAnnotations elementType) + internal static bool IsSpanOrListType(CSharpCompilation compilation, TypeSymbol targetType, WellKnownType spanType, out TypeWithAnnotations elementType) { if (targetType is NamedTypeSymbol { Arity: 1 } namedType && ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(spanType))) @@ -1716,6 +1735,21 @@ internal static bool IsSpanOrListType(CSharpCompilation compilation, TypeSymbol elementType = default; return false; } + + internal static bool IsKeyValuePairType(CSharpCompilation compilation, TypeSymbol? targetType, WellKnownType keyValuePairType, out TypeWithAnnotations keyType, out TypeWithAnnotations valueType) + { + if (targetType is NamedTypeSymbol { Arity: 2 } namedType + && ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(keyValuePairType))) + { + var typeArguments = namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; + keyType = typeArguments[0]; + valueType = typeArguments[1]; + return true; + } + keyType = default; + valueType = default; + return false; + } #nullable disable internal Conversion ClassifyImplicitUserDefinedConversionForV6SwitchGoverningType(TypeSymbol sourceType, out TypeSymbol switchGoverningType, ref CompoundUseSiteInfo useSiteInfo) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index b68f25b9d7efe..1d7f70f9c0dd6 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1952,6 +1952,11 @@ + + + + + diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 342f70c8c5582..224c9827ad8af 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests @@ -157,5 +158,188 @@ .locals init (System.Collections.Generic.Dictionary V_0, } """); } + + // PROTOTYPE: Test order of evaluation and side-effects for k:v pairs. + + [Fact] + public void InferredType_ExpressionElement() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + IDictionary d = [default, new()]; + d.Report(); + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: "[1:one, 2:two, 3:three]"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void InferredType_KeyValueElement() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + IDictionary d = [default:default, null:new()]; + d.Report(); + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: "[1:one, 2:two, 3:three]"); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void ConversionError_ExpressionElement() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + var x = new object(); + IDictionary d = [x, null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (7,36): error CS0029: Cannot implicitly convert type 'object' to 'System.Collections.Generic.KeyValuePair' + // IDictionary z = [x, null]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "System.Collections.Generic.KeyValuePair").WithLocation(7, 36), + // (7,39): error CS0037: Cannot convert null to 'KeyValuePair' because it is a non-nullable value type + // IDictionary z = [x, null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("System.Collections.Generic.KeyValuePair").WithLocation(7, 39)); + } + + [Fact] + public void ConversionError_KeyValueElement_01() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + var x = new object(); + var y = new object(); + IDictionary d = [x:1, "2":y]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (8,39): error CS0029: Cannot implicitly convert type 'object' to 'string' + // IDictionary d = [x:1, "2":y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "string").WithLocation(8, 39), + // (8,48): error CS0029: Cannot implicitly convert type 'object' to 'int' + // IDictionary d = [x:1, "2":y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("object", "int").WithLocation(8, 48)); + } + + [Fact] + public void ConversionError_KeyValueElement_02() + { + // PROTOTYPE: Why aren't we reporting an error for new() since string doesn't have an + // empty .ctor? Compare with ConversionError_PROTOTYPE_01 below. + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + IDictionary d = [new():null]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,45): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type + // IDictionary d = [new():null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(6, 45)); + } + + [Fact] + public void ConversionError_PROTOTYPE_01() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + IList z = [new()]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,28): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // IList z = [new()]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new()").WithArguments("string", "0").WithLocation(6, 28)); + } + + [Fact] + public void ConversionError_SpreadElement() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + IDictionary d = [..new()]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,41): error CS8754: There is no target type for 'new()' + // IDictionary d = [..new()]; + Diagnostic(ErrorCode.ERR_ImplicitObjectCreationNoTargetType, "new()").WithArguments("new()").WithLocation(6, 41)); + } + + [Fact] + public void Lock() + { + string source = """ + using System.Collections.Generic; + using System.Threading; + class Program + { + static void Main() + { + var x = new Lock(); + var y = new Lock(); + object[] a = [x]; + IDictionary d = [x:1, 2:y]; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90); + comp.VerifyEmitDiagnostics( + // (9,23): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // object[] a = [x]; + Diagnostic(ErrorCode.WRN_ConvertingLock, "x").WithLocation(9, 23), + // (10,42): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // IDictionary d = [x:1, 2:y]; + Diagnostic(ErrorCode.WRN_ConvertingLock, "x").WithLocation(10, 42), + // (10,49): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // IDictionary d = [x:1, 2:y]; + Diagnostic(ErrorCode.WRN_ConvertingLock, "y").WithLocation(10, 49)); + } } } From 7464ae06bc5245c087dd1da647862578ab0401ea Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:33:34 -0800 Subject: [PATCH 06/16] Formatting --- .../Semantics/DictionaryExpressionTests.cs | 118 ++++++------------ 1 file changed, 35 insertions(+), 83 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 224c9827ad8af..8f8af20ea8c8e 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -166,19 +166,10 @@ public void InferredType_ExpressionElement() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - IDictionary d = [default, new()]; - d.Report(); - } - } + IDictionary d = [default, new()]; """; - var verifier = CompileAndVerify( - [source, s_dictionaryExtensions], - expectedOutput: "[1:one, 2:two, 3:three]"); - verifier.VerifyDiagnostics(); + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -186,19 +177,10 @@ public void InferredType_KeyValueElement() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - IDictionary d = [default:default, null:new()]; - d.Report(); - } - } + IDictionary d = [default:default, null:new()]; """; - var verifier = CompileAndVerify( - [source, s_dictionaryExtensions], - expectedOutput: "[1:one, 2:two, 3:three]"); - verifier.VerifyDiagnostics(); + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); } [Fact] @@ -206,23 +188,17 @@ public void ConversionError_ExpressionElement() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - var x = new object(); - IDictionary d = [x, null]; - } - } + var x = new object(); + IDictionary d = [x, null]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (7,36): error CS0029: Cannot implicitly convert type 'object' to 'System.Collections.Generic.KeyValuePair' - // IDictionary z = [x, null]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "System.Collections.Generic.KeyValuePair").WithLocation(7, 36), - // (7,39): error CS0037: Cannot convert null to 'KeyValuePair' because it is a non-nullable value type - // IDictionary z = [x, null]; - Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("System.Collections.Generic.KeyValuePair").WithLocation(7, 39)); + // (3,28): error CS0029: Cannot implicitly convert type 'object' to 'System.Collections.Generic.KeyValuePair' + // IDictionary d = [x, null]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "System.Collections.Generic.KeyValuePair").WithLocation(3, 28), + // (3,31): error CS0037: Cannot convert null to 'KeyValuePair' because it is a non-nullable value type + // IDictionary d = [x, null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("System.Collections.Generic.KeyValuePair").WithLocation(3, 31)); } [Fact] @@ -230,24 +206,18 @@ public void ConversionError_KeyValueElement_01() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - var x = new object(); - var y = new object(); - IDictionary d = [x:1, "2":y]; - } - } + var x = new object(); + var y = new object(); + IDictionary d = [x:1, "2":y]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (8,39): error CS0029: Cannot implicitly convert type 'object' to 'string' - // IDictionary d = [x:1, "2":y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "string").WithLocation(8, 39), - // (8,48): error CS0029: Cannot implicitly convert type 'object' to 'int' - // IDictionary d = [x:1, "2":y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("object", "int").WithLocation(8, 48)); + // (4,31): error CS0029: Cannot implicitly convert type 'object' to 'string' + // IDictionary d = [x:1, "2":y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "string").WithLocation(4, 31), + // (4,40): error CS0029: Cannot implicitly convert type 'object' to 'int' + // IDictionary d = [x:1, "2":y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("object", "int").WithLocation(4, 40)); } [Fact] @@ -257,19 +227,13 @@ public void ConversionError_KeyValueElement_02() // empty .ctor? Compare with ConversionError_PROTOTYPE_01 below. string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - IDictionary d = [new():null]; - } - } + IDictionary d = [new():null]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (6,45): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type - // IDictionary d = [new():null]; - Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(6, 45)); + // (2,37): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type + // IDictionary d = [new():null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(2, 37)); } [Fact] @@ -277,19 +241,13 @@ public void ConversionError_PROTOTYPE_01() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - IList z = [new()]; - } - } + IList z = [new()]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (6,28): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // IList z = [new()]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new()").WithArguments("string", "0").WithLocation(6, 28)); + // (2,20): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // IList z = [new()]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new()").WithArguments("string", "0").WithLocation(2, 20)); } [Fact] @@ -297,19 +255,13 @@ public void ConversionError_SpreadElement() { string source = """ using System.Collections.Generic; - class Program - { - static void Main() - { - IDictionary d = [..new()]; - } - } + IDictionary d = [..new()]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (6,41): error CS8754: There is no target type for 'new()' - // IDictionary d = [..new()]; - Diagnostic(ErrorCode.ERR_ImplicitObjectCreationNoTargetType, "new()").WithArguments("new()").WithLocation(6, 41)); + // (2,33): error CS8754: There is no target type for 'new()' + // IDictionary d = [..new()]; + Diagnostic(ErrorCode.ERR_ImplicitObjectCreationNoTargetType, "new()").WithArguments("new()").WithLocation(2, 33)); } [Fact] From 767c1c9ee20edf637d9e0b49ca2c87d29d2515ec Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:01:01 -0800 Subject: [PATCH 07/16] Remove incomplete key-value pair conversion --- .../Portable/Binder/Binder_Conversions.cs | 96 ++++++-------- .../Semantics/Conversions/Conversion.cs | 5 - .../Semantics/Conversions/ConversionKind.cs | 1 - .../Conversions/ConversionKindExtensions.cs | 1 - .../Semantics/Conversions/Conversions.cs | 118 ++++++------------ .../LocalRewriter_CollectionExpression.cs | 14 +-- .../CSharp/Portable/PublicAPI.Unshipped.txt | 1 - .../Semantics/DictionaryExpressionTests.cs | 105 ++++++++++------ 8 files changed, 145 insertions(+), 196 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 590068f1f58ba..b8b567ef92714 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -277,9 +277,6 @@ BoundExpression createConversion( { ensureAllUnderlyingConversionsChecked(syntax, source, conversion, wasCompilerGenerated, destination, diagnostics); - // PROTOTYPE: We're not going through this code path with the individual Key and Value - // conversions for a KeyValuePair conversion, so we won't report this warning, when - // converting Key from Lock to object for instance. if (conversion.Kind is ConversionKind.ImplicitReference or ConversionKind.ExplicitReference && source.Type is { } sourceType && sourceType.IsWellKnownTypeLock()) @@ -403,34 +400,6 @@ void ensureAllUnderlyingConversionsChecked(SyntaxNode syntax, BoundExpression so Debug.Assert(conversion.UnderlyingConversions.IsDefault); conversion.MarkUnderlyingConversionsChecked(); } - else if (conversion.IsKeyValuePair) - { - if (!ConversionsBase.IsKeyValuePairType(Compilation, source.Type, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var sourceKeyType, out var sourceValueType) || - !ConversionsBase.IsKeyValuePairType(Compilation, destination, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var destinationKeyType, out var destinationValueType)) - { - throw ExceptionUtilities.UnexpectedValue(source.Type); - } - // PROTOTYPE: Test that we're reporting errors from this code path. - _ = CreateConversion( - syntax, - new BoundValuePlaceholder(source.Syntax, sourceKeyType.Type), - conversion.UnderlyingConversions[0], - isCast: false, - conversionGroupOpt: null, - wasCompilerGenerated: true, - destinationKeyType.Type, - diagnostics); - _ = CreateConversion( - syntax, - new BoundValuePlaceholder(source.Syntax, sourceValueType.Type), - conversion.UnderlyingConversions[1], - isCast: false, - conversionGroupOpt: null, - wasCompilerGenerated: true, - destinationValueType.Type, - diagnostics); - conversion.MarkUnderlyingConversionsChecked(); - } conversion.AssertUnderlyingConversionsCheckedRecursive(); } @@ -974,38 +943,45 @@ private BoundCollectionExpression ConvertCollectionExpression( var elementConversions = conversion.UnderlyingConversions; Debug.Assert(elementType is { }); - Debug.Assert(elements.Length == elementConversions.Length); + Debug.Assert(elements.Length <= elementConversions.Length); Debug.Assert(elementConversions.All(c => c.Exists)); - for (int i = 0; i < elements.Length; i++) + int conversionIndex = 0; + foreach (var element in elements) { - var element = elements[i]; - var elementConversion = elementConversions[i]; - var convertedElement = element switch + BoundNode convertedElement; + switch (element) { - BoundCollectionExpressionSpreadElement spreadElement => - bindSpreadElement( + case BoundCollectionExpressionSpreadElement spreadElement: + convertedElement = bindSpreadElement( spreadElement, elementType, - elementConversion, - diagnostics), - BoundKeyValuePairElement keyValuePairElement => - bindKeyValuePairElement( + elementConversions[conversionIndex++], + diagnostics); + break; + case BoundKeyValuePairElement keyValuePairElement: + convertedElement = bindKeyValuePairElement( keyValuePairElement, elementType, - elementConversion, - diagnostics), - _ => CreateConversion( - element.Syntax, - (BoundExpression)element, - elementConversion, - isCast: false, - conversionGroupOpt: null, - destination: elementType, - diagnostics) - }; + elementConversions[conversionIndex], + elementConversions[conversionIndex + 1], + diagnostics); + conversionIndex += 2; + break; + default: + convertedElement = CreateConversion( + element.Syntax, + (BoundExpression)element, + elementConversions[conversionIndex++], + isCast: false, + conversionGroupOpt: null, + destination: elementType, + diagnostics); + break; + } builder.Add(convertedElement!); } + Debug.Assert(conversionIndex == elementConversions.Length); conversion.MarkUnderlyingConversionsChecked(); } @@ -1050,30 +1026,28 @@ BoundNode bindSpreadElement(BoundCollectionExpressionSpreadElement element, Type lengthOrCount: element.LengthOrCount); } - BoundNode bindKeyValuePairElement(BoundKeyValuePairElement element, TypeSymbol elementType, Conversion elementConversion, BindingDiagnosticBag diagnostics) + BoundNode bindKeyValuePairElement(BoundKeyValuePairElement element, TypeSymbol elementType, Conversion keyConversion, Conversion valueConversion, BindingDiagnosticBag diagnostics) { Debug.Assert(ReferenceEquals(elementType.OriginalDefinition, Compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV))); - Debug.Assert(elementConversion is { Kind: ConversionKind.KeyValuePair, UnderlyingConversions.Length: 2 }); var typeArguments = ((NamedTypeSymbol)elementType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; - var underlyingConversions = elementConversion.UnderlyingConversions; - var keyConversion = CreateConversion( + var convertedKey = CreateConversion( element.Key.Syntax, element.Key, - underlyingConversions[0], + keyConversion, isCast: false, conversionGroupOpt: null, destination: typeArguments[0].Type, diagnostics); - var valueConversion = CreateConversion( + var convertedValue = CreateConversion( element.Value.Syntax, element.Value, - underlyingConversions[1], + valueConversion, isCast: false, conversionGroupOpt: null, destination: typeArguments[1].Type, diagnostics); - return element.Update(keyConversion, valueConversion); + return element.Update(convertedKey, convertedValue); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index dc05b218fd9a4..731dcf784e037 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -714,11 +714,6 @@ public bool IsObjectCreation /// public bool IsCollectionExpression => Kind == ConversionKind.CollectionExpression; - /// - /// Returns true if the conversion is an implicit conversion of a key:value pair in a collection expression. - /// - public bool IsKeyValuePair => Kind == ConversionKind.KeyValuePair; // PROTOTYPE: Add key-value pair conversion to spec. - /// /// Returns true if the conversion is an implicit switch expression conversion. /// diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs index b8afd0fa4d760..e60ed4d88b65f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs @@ -64,7 +64,6 @@ internal enum ConversionKind : byte DefaultLiteral, // a conversion from a `default` literal to any type ObjectCreation, // a conversion from a `new()` expression to any type CollectionExpression, // a conversion from a collection expression to any type - KeyValuePair, // a conversion from a k:v to a KeyValuePair<,> InterpolatedStringHandler, // A conversion from an interpolated string literal to a type attributed with InterpolatedStringBuilderAttribute diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs index 7f9a027de2e3f..b25b4bef8aaa6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs @@ -54,7 +54,6 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind) case InlineArray: case CollectionExpression: case ImplicitSpan: - case KeyValuePair: return true; case ExplicitNumeric: diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index dc556e8ee7a78..530fe9da389ed 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -204,35 +204,47 @@ protected override Conversion GetCollectionExpressionConversion( var builder = ArrayBuilder.GetInstance(elements.Length); foreach (var element in elements) { - Conversion elementConversion; - if (usesKeyValuePairs) + switch (element) { - // PROTOTYPE: Check language version. - Debug.Assert(keyType.Type is { }); - Debug.Assert(valueType.Type is { }); - elementConversion = element switch - { - BoundCollectionExpressionSpreadElement spreadElement => GetSpreadElementAsKeyValuePairConversion(spreadElement, keyType.Type, valueType.Type, ref useSiteInfo), - BoundKeyValuePairElement keyValuePairElement => GetKeyValuePairElementConversion(keyValuePairElement, keyType.Type, valueType.Type, ref useSiteInfo), - _ => GetExpressionElementAsKeyValuePairConversion((BoundExpression)element, keyType.Type, valueType.Type, ref useSiteInfo), - }; - } - else - { - elementConversion = element switch - { - BoundCollectionExpressionSpreadElement spreadElement => GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo), - _ => ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo), - }; - } - - if (!elementConversion.Exists) - { - builder.Free(); - return Conversion.NoConversion; + case BoundCollectionExpressionSpreadElement spreadElement: + { + var elementConversion = GetCollectionExpressionSpreadElementConversion(spreadElement, elementType, ref useSiteInfo); + if (elementConversion.Exists) + { + builder.Add(elementConversion); + continue; + } + } + break; + case BoundKeyValuePairElement keyValuePairElement: + { + // PROTOTYPE: Check language version. + if (usesKeyValuePairs) + { + var keyConversion = ClassifyImplicitConversionFromExpression(keyValuePairElement.Key, keyType.Type, ref useSiteInfo); + var valueConversion = ClassifyImplicitConversionFromExpression(keyValuePairElement.Value, valueType.Type, ref useSiteInfo); + if (keyConversion.Exists && valueConversion.Exists) + { + builder.Add(keyConversion); + builder.Add(valueConversion); + continue; + } + } + } + break; + default: + { + var elementConversion = ClassifyImplicitConversionFromExpression((BoundExpression)element, elementType, ref useSiteInfo); + if (elementConversion.Exists) + { + builder.Add(elementConversion); + continue; + } + } + break; } - - builder.Add(elementConversion); + builder.Free(); + return Conversion.NoConversion; } return Conversion.CreateCollectionExpressionConversion(collectionTypeKind, elementType, constructor, isExpanded, builder.ToImmutableAndFree()); @@ -255,58 +267,6 @@ internal Conversion GetCollectionExpressionSpreadElementConversion( targetType, ref useSiteInfo); } - - private Conversion GetExpressionElementAsKeyValuePairConversion( - BoundExpression element, - TypeSymbol keyType, - TypeSymbol valueType, - ref CompoundUseSiteInfo useSiteInfo) - { - // PROTOTYPE: Test expression element with no type. - if (IsKeyValuePairType(Compilation, element.Type, WellKnownType.System_Collections_Generic_KeyValuePair_KV, out var elementKeyType, out var elementValueType)) - { - var key = ClassifyImplicitConversionFromType(elementKeyType.Type, keyType, ref useSiteInfo); - var value = ClassifyImplicitConversionFromType(elementValueType.Type, valueType, ref useSiteInfo); - if (key.Exists && value.Exists) - { - return new Conversion(ConversionKind.KeyValuePair, [key, value]); - } - } - return Conversion.NoConversion; - } - - private Conversion GetSpreadElementAsKeyValuePairConversion( - BoundCollectionExpressionSpreadElement element, - TypeSymbol keyType, - TypeSymbol valueType, - ref CompoundUseSiteInfo useSiteInfo) - { - var enumeratorInfo = element.EnumeratorInfoOpt; - if (enumeratorInfo is null) - { - return Conversion.NoConversion; - } - return GetExpressionElementAsKeyValuePairConversion( - new BoundValuePlaceholder(element.Syntax, enumeratorInfo.ElementType), - keyType, - valueType, - ref useSiteInfo); - } - - private Conversion GetKeyValuePairElementConversion( - BoundKeyValuePairElement element, - TypeSymbol keyType, - TypeSymbol valueType, - ref CompoundUseSiteInfo useSiteInfo) - { - var key = ClassifyImplicitConversionFromExpression(element.Key, keyType, ref useSiteInfo); - var value = ClassifyImplicitConversionFromExpression(element.Value, valueType, ref useSiteInfo); - if (key.Exists && value.Exists) - { - return new Conversion(ConversionKind.KeyValuePair, [key, value]); - } - return Conversion.NoConversion; - } #nullable disable /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 13a5fb8691d79..a18befe426522 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -1216,12 +1216,6 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no sideEffects.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); } break; - case BoundConversion { ConversionKind: ConversionKind.KeyValuePair, Operand: var expression }: - { - var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair(expression, addMethod, sideEffects, localsBuilder); - sideEffects.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); - } - break; case BoundCollectionExpressionSpreadElement spreadElement: var rewrittenExpression = VisitExpression(spreadElement.Expression); var rewrittenElement = MakeCollectionExpressionSpreadElement( @@ -1229,7 +1223,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no rewrittenExpression, iteratorBody => { - var expression = ((BoundConversion)((BoundExpressionStatement)iteratorBody).Expression).Operand; + var expression = ((BoundExpressionStatement)iteratorBody).Expression; var builder = ArrayBuilder.GetInstance(); var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair(expression, addMethod, builder, localsBuilder); builder.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); @@ -1243,7 +1237,11 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no sideEffects.Add(rewrittenElement); break; default: - throw ExceptionUtilities.UnexpectedValue(element); + { + var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair((BoundExpression)element, addMethod, sideEffects, localsBuilder); + sideEffects.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); + } + break; } } diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index c191fd5ef34e4..30787971f37f1 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ -Microsoft.CodeAnalysis.CSharp.Conversion.IsKeyValuePair.get -> bool Microsoft.CodeAnalysis.CSharp.Conversion.IsSpan.get -> bool Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp13 = 1300 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion Microsoft.CodeAnalysis.CSharp.Syntax.KeyValuePairElementSyntax diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 8f8af20ea8c8e..e0b0d66f6c8e1 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -90,7 +90,7 @@ static void Main() var y = new KeyValuePair(3, "three"); F(x, new[] { y }).Report(); } - static IDictionary F(KeyValuePair x, IEnumerable> y) + static IDictionary F(KeyValuePair x, IEnumerable> y) { return [1:"one", x, ..y]; } @@ -102,63 +102,88 @@ static IDictionary F(KeyValuePair x, IEnumerable V_0, + .locals init (System.Collections.Generic.Dictionary V_0, System.Collections.Generic.KeyValuePair V_1, System.Collections.Generic.KeyValuePair V_2, System.Collections.Generic.IEnumerator> V_3) - IL_0000: newobj "System.Collections.Generic.Dictionary..ctor()" + IL_0000: newobj "System.Collections.Generic.Dictionary..ctor()" IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldc.i4.1 - IL_0008: conv.i8 - IL_0009: ldstr "one" - IL_000e: callvirt "void System.Collections.Generic.Dictionary.Add(long, object)" - IL_0013: ldarg.0 - IL_0014: stloc.1 - IL_0015: ldloc.0 - IL_0016: ldloca.s V_1 - IL_0018: call "int System.Collections.Generic.KeyValuePair.Key.get" - IL_001d: conv.i8 - IL_001e: ldloca.s V_1 - IL_0020: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_0025: callvirt "void System.Collections.Generic.Dictionary.Add(long, object)" - IL_002a: ldarg.1 - IL_002b: callvirt "System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()" - IL_0030: stloc.3 + IL_0008: ldstr "one" + IL_000d: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0012: ldarg.0 + IL_0013: stloc.1 + IL_0014: ldloc.0 + IL_0015: ldloca.s V_1 + IL_0017: call "int System.Collections.Generic.KeyValuePair.Key.get" + IL_001c: ldloca.s V_1 + IL_001e: call "string System.Collections.Generic.KeyValuePair.Value.get" + IL_0023: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0028: ldarg.1 + IL_0029: callvirt "System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()" + IL_002e: stloc.3 .try { - IL_0031: br.s IL_004f - IL_0033: ldloc.3 - IL_0034: callvirt "System.Collections.Generic.KeyValuePair System.Collections.Generic.IEnumerator>.Current.get" - IL_0039: stloc.2 - IL_003a: ldloc.0 - IL_003b: ldloca.s V_2 - IL_003d: call "int System.Collections.Generic.KeyValuePair.Key.get" - IL_0042: conv.i8 - IL_0043: ldloca.s V_2 - IL_0045: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_004a: callvirt "void System.Collections.Generic.Dictionary.Add(long, object)" - IL_004f: ldloc.3 - IL_0050: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_0055: brtrue.s IL_0033 - IL_0057: leave.s IL_0063 + IL_002f: br.s IL_004c + IL_0031: ldloc.3 + IL_0032: callvirt "System.Collections.Generic.KeyValuePair System.Collections.Generic.IEnumerator>.Current.get" + IL_0037: stloc.2 + IL_0038: ldloc.0 + IL_0039: ldloca.s V_2 + IL_003b: call "int System.Collections.Generic.KeyValuePair.Key.get" + IL_0040: ldloca.s V_2 + IL_0042: call "string System.Collections.Generic.KeyValuePair.Value.get" + IL_0047: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_004c: ldloc.3 + IL_004d: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_0052: brtrue.s IL_0031 + IL_0054: leave.s IL_0060 } finally { + IL_0056: ldloc.3 + IL_0057: brfalse.s IL_005f IL_0059: ldloc.3 - IL_005a: brfalse.s IL_0062 - IL_005c: ldloc.3 - IL_005d: callvirt "void System.IDisposable.Dispose()" - IL_0062: endfinally + IL_005a: callvirt "void System.IDisposable.Dispose()" + IL_005f: endfinally } - IL_0063: ldloc.0 - IL_0064: ret + IL_0060: ldloc.0 + IL_0061: ret } """); } + [Fact] + public void KeyValueConversions() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + var x = new KeyValuePair(2, "two"); + var y = new KeyValuePair(3, "three"); + } + static IDictionary F(KeyValuePair x, IEnumerable> y) + { + return [x, ..y]; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (11,17): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' + // return [x, ..y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(11, 17), + // (11,22): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' + // return [x, ..y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(11, 22)); + } + // PROTOTYPE: Test order of evaluation and side-effects for k:v pairs. [Fact] From 2044601840bfc0429ab8d22203b87bf2909c56af Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:11:09 -0800 Subject: [PATCH 08/16] IOperation --- .../Portable/Operations/CSharpOperationFactory.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 3d35c0bbbe2bb..834dba9d382db 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -304,6 +304,7 @@ public CSharpOperationFactory(SemanticModel semanticModel) case BoundKind.StackAllocArrayCreation: case BoundKind.TypeExpression: case BoundKind.TypeOrValueExpression: + case BoundKind.KeyValuePairElement: // PROTOTYPE: Implement. ConstantValue? constantValue = (boundNode as BoundExpression)?.ConstantValueOpt; bool isImplicit = boundNode.WasCompilerGenerated; @@ -1242,6 +1243,7 @@ private ICollectionExpressionOperation CreateBoundCollectionExpression(BoundColl case CollectionExpressionTypeKind.None: case CollectionExpressionTypeKind.Array: case CollectionExpressionTypeKind.ArrayInterface: + case CollectionExpressionTypeKind.DictionaryInterface: case CollectionExpressionTypeKind.ReadOnlySpan: case CollectionExpressionTypeKind.Span: return null; @@ -1257,9 +1259,12 @@ private ICollectionExpressionOperation CreateBoundCollectionExpression(BoundColl private IOperation CreateBoundCollectionExpressionElement(BoundCollectionExpression expr, BoundNode element) { - return element is BoundCollectionExpressionSpreadElement spreadElement ? - CreateBoundCollectionExpressionSpreadElement(expr, spreadElement) : - Create(Binder.GetUnderlyingCollectionExpressionElement(expr, (BoundExpression)element, throwOnErrors: false)); + return element switch + { + BoundCollectionExpressionSpreadElement spreadElement => CreateBoundCollectionExpressionSpreadElement(expr, spreadElement), + BoundKeyValuePairElement keyValuePairElement => Create(keyValuePairElement), + _ => Create(Binder.GetUnderlyingCollectionExpressionElement(expr, (BoundExpression)element, throwOnErrors: false)) + }; } private ISpreadOperation CreateBoundCollectionExpressionSpreadElement(BoundCollectionExpression expr, BoundCollectionExpressionSpreadElement element) From df3074ace9042ed7290bf6d3d0b0ec06f76ab060 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:54:14 -0800 Subject: [PATCH 09/16] Misc. --- .../Semantics/Conversions/ConversionsBase.cs | 4 +- .../LocalRewriter_CollectionExpression.cs | 2 +- .../Semantics/DictionaryExpressionTests.cs | 78 ++++++++++++------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 5b24d40697a3b..8792097fec9a1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -1710,9 +1710,9 @@ static bool implementsSpecialInterface(CSharpCompilation compilation, TypeSymbol static bool isDictionaryInterface(CSharpCompilation compilation, TypeSymbol targetType, out TypeWithAnnotations elementType) { - // PROTOTYPE: Support IReadOnlyDictionary as well. if (targetType is NamedTypeSymbol { Arity: 2 } namedType && - ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IDictionary_KV))) + (ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IDictionary_KV)) || + ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IReadOnlyDictionary_KV)))) { // PROTOTYPE: Test with missing KeyValuePair<,>. elementType = TypeWithAnnotations.Create(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV). diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index a18befe426522..fba37ea935df6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -464,7 +464,7 @@ private BoundExpression VisitDictionaryInterfaceCollectionExpression(BoundCollec var collectionType = (NamedTypeSymbol)node.Type; var typeArguments = collectionType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; var elements = node.Elements; - // PROTOTYPE: Should we create a custom IDictionary implementation, + // PROTOTYPE: Should we create a custom interface implementation, // at least to enforce immutability for IReadOnlyDictionary? var collection = CreateAndPopulateDictionary(node, typeArguments, elements); return _factory.Convert(collectionType, collection); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index e0b0d66f6c8e1..734f7909eec72 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -39,7 +39,36 @@ internal static void Report(this IEnumerable> e) } """; - // PROTOTYPE: Test language version. + [Theory] + [InlineData(LanguageVersion.CSharp13)] + [InlineData(LanguageVersion.Preview)] + public void LanguageVersionDiagnostics_01(LanguageVersion languageVersion) + { + string source = """ + using System.Collections.Generic; + IDictionary d; + d = []; + d = [1:"one"]; + var x = new KeyValuePair(2, "two"); + var y = new KeyValuePair[] { new(3, "three") }; + d = [x]; + d = [..y]; + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + if (languageVersion == LanguageVersion.CSharp13) + { + // PROTOTYPE: Should report language version errors for all d = ... assignments. + comp.VerifyEmitDiagnostics( + // (4,7): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, ":").WithArguments("dictionary expressions").WithLocation(4, 7)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + } + [Fact] public void Dictionary() { @@ -50,10 +79,10 @@ class Program static void Main() { var x = new KeyValuePair(2, "two"); - var y = new KeyValuePair(3, "three"); - F(x, new[] { y }).Report(); + var y = new KeyValuePair[] { new(3, "three") }; + F(x, y).Report(); } - static Dictionary F(KeyValuePair x, IEnumerable> y) + static Dictionary F(KeyValuePair x, IEnumerable> y) { return [1:"one", x, ..y]; } @@ -61,36 +90,30 @@ static Dictionary F(KeyValuePair x, IEnumerable' must have an instance or extension method 'Add' that can be called with a single argument. - // return [1:"one", x, ..y]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, @"[1:""one"", x, ..y]").WithArguments("System.Collections.Generic.Dictionary").WithLocation(12, 16), - // (12,17): error CS9268: Collection expression type 'Dictionary' does not support key-value pair elements. + // (12,16): error CS9215: Collection expression type 'Dictionary' must have an instance or extension method 'Add' that can be called with a single argument. // return [1:"one", x, ..y]; - Diagnostic(ErrorCode.ERR_CollectionExpressionKeyValuePairNotSupported, @"1:""one""").WithArguments("System.Collections.Generic.Dictionary").WithLocation(12, 17), - // (12,26): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, @"[1:""one"", x, ..y]").WithArguments("System.Collections.Generic.Dictionary").WithLocation(12, 16), + // (12,17): error CS9268: Collection expression type 'Dictionary' does not support key-value pair elements. // return [1:"one", x, ..y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(12, 26), - // (12,31): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' - // return [1:"one", x, ..y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(12, 31)); + Diagnostic(ErrorCode.ERR_CollectionExpressionKeyValuePairNotSupported, @"1:""one""").WithArguments("System.Collections.Generic.Dictionary").WithLocation(12, 17)); } - // PROTOTYPE: Test language version. - // PROTOTYPE: Test IReadOnlyDictionary<,>. - [Fact] - public void DictionaryInterface() + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void DictionaryInterface(string typeName) { - string source = """ + string source = $$""" using System.Collections.Generic; class Program { static void Main() { var x = new KeyValuePair(2, "two"); - var y = new KeyValuePair(3, "three"); - F(x, new[] { y }).Report(); + var y = new KeyValuePair[] { new(3, "three") }; + F(x, y).Report(); } - static IDictionary F(KeyValuePair x, IEnumerable> y) + static {{typeName}} F(KeyValuePair x, IEnumerable> y) { return [1:"one", x, ..y]; } @@ -166,7 +189,8 @@ class Program static void Main() { var x = new KeyValuePair(2, "two"); - var y = new KeyValuePair(3, "three"); + var y = new KeyValuePair[] { new(3, "three") }; + F(x, y); } static IDictionary F(KeyValuePair x, IEnumerable> y) { @@ -176,12 +200,12 @@ static IDictionary F(KeyValuePair x, IEnumerable' to 'System.Collections.Generic.KeyValuePair' + // (12,17): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' // return [x, ..y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(11, 17), - // (11,22): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(12, 17), + // (12,22): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.KeyValuePair' to 'System.Collections.Generic.KeyValuePair' // return [x, ..y]; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(11, 22)); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "y").WithArguments("System.Collections.Generic.KeyValuePair", "System.Collections.Generic.KeyValuePair").WithLocation(12, 22)); } // PROTOTYPE: Test order of evaluation and side-effects for k:v pairs. From 006323abb211fad6d98875240396a34173718dd6 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 8 Dec 2024 16:28:58 -0800 Subject: [PATCH 10/16] PROTOTYPE comments --- .../Portable/Binder/Binder_Conversions.cs | 19 +- .../Portable/Binder/Binder_Expressions.cs | 2 - .../Semantics/Conversions/Conversions.cs | 3 +- .../Semantics/Conversions/ConversionsBase.cs | 1 - .../LocalRewriter_CollectionExpression.cs | 8 +- .../Operations/CSharpOperationFactory.cs | 2 +- .../Semantics/DictionaryExpressionTests.cs | 304 +++++++++++++++++- 7 files changed, 305 insertions(+), 34 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index b8b567ef92714..32f3bab7f41fc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -873,6 +873,10 @@ private BoundCollectionExpression ConvertCollectionExpression( return BindCollectionExpressionForErrorRecovery(node, targetType, inConversion: true, diagnostics); } break; + + case CollectionExpressionTypeKind.DictionaryInterface: + MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax); + break; } var elements = node.Elements; @@ -928,12 +932,19 @@ private BoundCollectionExpression ConvertCollectionExpression( } else { - if ((collectionTypeKind is CollectionExpressionTypeKind.ArrayInterface) || + // Verify the existence of the well-known members that may be used in lowering, even + // though not all will be used for any particular collection expression. Checking all + // gives a consistent behavior, regardless of collection expression elements. + if (collectionTypeKind is CollectionExpressionTypeKind.DictionaryInterface) + { + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor, diagnostics, syntax: syntax); + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add, diagnostics, syntax: syntax); + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key, diagnostics, syntax: syntax); + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value, diagnostics, syntax: syntax); + } + else if ((collectionTypeKind is CollectionExpressionTypeKind.ArrayInterface) || node.HasSpreadElements(out _, out _)) { - // Verify the existence of the List members that may be used in lowering, even - // though not all will be used for any particular collection expression. Checking all - // gives a consistent behavior, regardless of collection expression elements. _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctor, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctorInt32, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__Add, diagnostics, syntax: syntax); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 10ad4540d3840..1793ac6906f7b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5278,8 +5278,6 @@ static BoundNode bindSpreadElement(SpreadElementSyntax syntax, BindingDiagnostic private BoundNode BindKeyValuePair(KeyValuePairElementSyntax syntax, BindingDiagnosticBag diagnostics) { MessageID.IDS_FeatureDictionaryExpressions.CheckFeatureAvailability(diagnostics, syntax, syntax.ColonToken.GetLocation()); - // PROTOTYPE: Test when key/value are not r-values. - // PROTOTYPE: Test when key/value do not have types. var key = BindValue(syntax.KeyExpression, diagnostics, BindValueKind.RValue); var value = BindValue(syntax.ValueExpression, diagnostics, BindValueKind.RValue); return new BoundKeyValuePairElement(syntax, key, value); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index 530fe9da389ed..39fd13300ab76 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -218,7 +218,6 @@ protected override Conversion GetCollectionExpressionConversion( break; case BoundKeyValuePairElement keyValuePairElement: { - // PROTOTYPE: Check language version. if (usesKeyValuePairs) { var keyConversion = ClassifyImplicitConversionFromExpression(keyValuePairElement.Key, keyType.Type, ref useSiteInfo); @@ -260,7 +259,7 @@ internal Conversion GetCollectionExpressionSpreadElementConversion( { return Conversion.NoConversion; } - // PROTOTYPE: This should be conversion from type rather than conversion + // This should be conversion from type rather than conversion // from expression. The difference is in handling of dynamic. return ClassifyImplicitConversionFromExpression( new BoundValuePlaceholder(element.Syntax, enumeratorInfo.ElementType), diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 8792097fec9a1..f8e759b4f9c02 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -1714,7 +1714,6 @@ static bool isDictionaryInterface(CSharpCompilation compilation, TypeSymbol targ (ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IDictionary_KV)) || ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IReadOnlyDictionary_KV)))) { - // PROTOTYPE: Test with missing KeyValuePair<,>. elementType = TypeWithAnnotations.Create(compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_KeyValuePair_KV). Construct(namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)); return true; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index fba37ea935df6..2dbde0fe7b8eb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -464,8 +464,7 @@ private BoundExpression VisitDictionaryInterfaceCollectionExpression(BoundCollec var collectionType = (NamedTypeSymbol)node.Type; var typeArguments = collectionType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; var elements = node.Elements; - // PROTOTYPE: Should we create a custom interface implementation, - // at least to enforce immutability for IReadOnlyDictionary? + // PROTOTYPE: Create a custom interface implementation for IReadOnlyDictionary to enforce immutability? var collection = CreateAndPopulateDictionary(node, typeArguments, elements); return _factory.Convert(collectionType, collection); } @@ -1189,8 +1188,6 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no var localsBuilder = ArrayBuilder.GetInstance(); var sideEffects = ArrayBuilder.GetInstance(elements.Length + 1); - // PROTOTYPE: Use RewriteCollectionExpressionElementsIntoTemporaries(). - // Dictionary dictionary = new(); var constructor = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor)).AsMember(collectionType); BoundObjectCreationExpression rewrittenReceiver = _factory.New(constructor, ImmutableArray.Empty); @@ -1202,11 +1199,9 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no sideEffects.Add(assignmentToTemp); var addMethod = _factory.WellKnownMethod(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add).AsMember(collectionType); - // PROTOTYPE: Use AddCollectionExpressionElements()? for (int i = 0; i < elements.Length; i++) { var element = elements[i]; - // PROTOTYPE: Use RewriteCollectionExpressionElementExpression()? switch (element) { case BoundKeyValuePairElement keyValuePairElement: @@ -1274,7 +1269,6 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no BoundLocal expressionTemp = _factory.StoreToTemp(VisitExpression(expression), out assignmentToTemp); localsBuilder.Add(expressionTemp); sideEffects.Add(assignmentToTemp); - // PROTOTYPE: Test with missing members. Presumably, these members should be checked in initial binding. var getKeyMethod = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key)).AsMember(sourceType); var getValueMethod = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value)).AsMember(sourceType); return (_factory.Convert(addMethod.Parameters[0].Type, _factory.Call(expressionTemp, getKeyMethod)), diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 834dba9d382db..c2d55a7257d16 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -304,7 +304,7 @@ public CSharpOperationFactory(SemanticModel semanticModel) case BoundKind.StackAllocArrayCreation: case BoundKind.TypeExpression: case BoundKind.TypeOrValueExpression: - case BoundKind.KeyValuePairElement: // PROTOTYPE: Implement. + case BoundKind.KeyValuePairElement: // PROTOTYPE: Implement IOperation support. ConstantValue? constantValue = (boundNode as BoundExpression)?.ConstantValueOpt; bool isImplicit = boundNode.WasCompilerGenerated; diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 734f7909eec72..3ef2877c9aef3 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -43,12 +43,37 @@ internal static void Report(this IEnumerable> e) [InlineData(LanguageVersion.CSharp13)] [InlineData(LanguageVersion.Preview)] public void LanguageVersionDiagnostics_01(LanguageVersion languageVersion) + { + string source = """ + using System.Collections.Generic; + IDictionary d = [1:"one"]; + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (2,30): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // IDictionary d = [1:"one"]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, @"[1:""one""]").WithArguments("dictionary expressions").WithLocation(2, 30), + // (2,32): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // IDictionary d = [1:"one"]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, ":").WithArguments("dictionary expressions").WithLocation(2, 32)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + } + + [Theory] + [InlineData(LanguageVersion.CSharp13)] + [InlineData(LanguageVersion.Preview)] + public void LanguageVersionDiagnostics_02(LanguageVersion languageVersion) { string source = """ using System.Collections.Generic; IDictionary d; d = []; - d = [1:"one"]; var x = new KeyValuePair(2, "two"); var y = new KeyValuePair[] { new(3, "three") }; d = [x]; @@ -57,11 +82,16 @@ public void LanguageVersionDiagnostics_01(LanguageVersion languageVersion) var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion == LanguageVersion.CSharp13) { - // PROTOTYPE: Should report language version errors for all d = ... assignments. comp.VerifyEmitDiagnostics( - // (4,7): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // d = [1:"one"]; - Diagnostic(ErrorCode.ERR_FeatureInPreview, ":").WithArguments("dictionary expressions").WithLocation(4, 7)); + // (3,5): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = []; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[]").WithArguments("dictionary expressions").WithLocation(3, 5), + // (6,5): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = [x]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[x]").WithArguments("dictionary expressions").WithLocation(6, 5), + // (7,5): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // d = [..y]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "[..y]").WithArguments("dictionary expressions").WithLocation(7, 5)); } else { @@ -179,6 +209,135 @@ .locals init (System.Collections.Generic.Dictionary V_0, """); } + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void DictionaryInterface_MissingMember(string typeName) + { + string source = $$""" + using System.Collections.Generic; + {{typeName}} d; + d = []; + d = [1:"one"]; + d = [new KeyValuePair(2, "two")]; + d = [.. new KeyValuePair[] { new(3, "three") }]; + """; + + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_List_T); + comp.VerifyEmitDiagnostics(); + + comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_Dictionary_KV); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(3, 5), + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(4, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(5, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(6, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(6, 5)); + + comp = CreateCompilation(source); + comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(6, 5)); + + comp = CreateCompilation(source); + comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(6, 5)); + } + + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void KeyValuePair_MissingMember(string typeName) + { + string source = $$""" + using System.Collections.Generic; + {{typeName}} d; + d = []; + d = [1:"one"]; + d = [new KeyValuePair(2, "two")]; + d = [.. new KeyValuePair[] { new(3, "three") }]; + """; + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + + comp = CreateCompilation(source); + comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(6, 5)); + + comp = CreateCompilation(source); + comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(6, 5)); + } + [Fact] public void KeyValueConversions() { @@ -208,7 +367,105 @@ static IDictionary F(KeyValuePair x, IEnumerable", "System.Collections.Generic.KeyValuePair").WithLocation(12, 22)); } - // PROTOTYPE: Test order of evaluation and side-effects for k:v pairs. + [Fact] + public void EvaluationOrder_01() + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void Main() + { + IReadOnlyDictionary d = [ + Identity(new KeyValuePair(1, "one")), + Identity(2):Identity("two"), + ..Identity((KeyValuePair[])[new(3, "three")])]; + d.Report(); + } + static T Identity(T value) + { + Console.WriteLine(value); + return value; + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: """ + [1, one] + 2 + two + System.Collections.Generic.KeyValuePair`2[System.Int32,System.String][] + [1:one, 2:two, 3:three] + """); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 149 (0x95) + .maxstack 5 + .locals init (System.Collections.Generic.Dictionary V_0, + System.Collections.Generic.KeyValuePair V_1, + System.Collections.Generic.KeyValuePair V_2, + System.Collections.Generic.KeyValuePair[] V_3, + int V_4) + IL_0000: newobj "System.Collections.Generic.Dictionary..ctor()" + IL_0005: stloc.0 + IL_0006: ldc.i4.1 + IL_0007: ldstr "one" + IL_000c: newobj "System.Collections.Generic.KeyValuePair..ctor(int, string)" + IL_0011: call "System.Collections.Generic.KeyValuePair Program.Identity>(System.Collections.Generic.KeyValuePair)" + IL_0016: stloc.1 + IL_0017: ldloc.0 + IL_0018: ldloca.s V_1 + IL_001a: call "int System.Collections.Generic.KeyValuePair.Key.get" + IL_001f: ldloca.s V_1 + IL_0021: call "string System.Collections.Generic.KeyValuePair.Value.get" + IL_0026: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_002b: ldloc.0 + IL_002c: ldc.i4.2 + IL_002d: call "int Program.Identity(int)" + IL_0032: ldstr "two" + IL_0037: call "string Program.Identity(string)" + IL_003c: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0041: ldc.i4.1 + IL_0042: newarr "System.Collections.Generic.KeyValuePair" + IL_0047: dup + IL_0048: ldc.i4.0 + IL_0049: ldc.i4.3 + IL_004a: ldstr "three" + IL_004f: newobj "System.Collections.Generic.KeyValuePair..ctor(int, string)" + IL_0054: stelem "System.Collections.Generic.KeyValuePair" + IL_0059: call "System.Collections.Generic.KeyValuePair[] Program.Identity[]>(System.Collections.Generic.KeyValuePair[])" + IL_005e: stloc.3 + IL_005f: ldc.i4.0 + IL_0060: stloc.s V_4 + IL_0062: br.s IL_0087 + IL_0064: ldloc.3 + IL_0065: ldloc.s V_4 + IL_0067: ldelem "System.Collections.Generic.KeyValuePair" + IL_006c: stloc.2 + IL_006d: ldloc.0 + IL_006e: ldloca.s V_2 + IL_0070: call "int System.Collections.Generic.KeyValuePair.Key.get" + IL_0075: ldloca.s V_2 + IL_0077: call "string System.Collections.Generic.KeyValuePair.Value.get" + IL_007c: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0081: ldloc.s V_4 + IL_0083: ldc.i4.1 + IL_0084: add + IL_0085: stloc.s V_4 + IL_0087: ldloc.s V_4 + IL_0089: ldloc.3 + IL_008a: ldlen + IL_008b: conv.i4 + IL_008c: blt.s IL_0064 + IL_008e: ldloc.0 + IL_008f: call "void DictionaryExtensions.Report(System.Collections.Generic.IEnumerable>)" + IL_0094: ret + } + """); + } [Fact] public void InferredType_ExpressionElement() @@ -272,31 +529,44 @@ public void ConversionError_KeyValueElement_01() [Fact] public void ConversionError_KeyValueElement_02() { - // PROTOTYPE: Why aren't we reporting an error for new() since string doesn't have an - // empty .ctor? Compare with ConversionError_PROTOTYPE_01 below. string source = """ using System.Collections.Generic; - IDictionary d = [new():null]; + IDictionary d; + d = [new():1]; + d = ["":null]; """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (2,37): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type - // IDictionary d = [new():null]; - Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(2, 37)); + // (3,6): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // d = [new():1]; + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new()").WithArguments("string", "0").WithLocation(3, 6), + // (4,9): error CS0037: Cannot convert null to 'int' because it is a non-nullable value type + // d = ["":null]; + Diagnostic(ErrorCode.ERR_ValueCantBeNull, "null").WithArguments("int").WithLocation(4, 9)); } [Fact] - public void ConversionError_PROTOTYPE_01() + public void ConversionError_KeyValueElement_03() { string source = """ using System.Collections.Generic; - IList z = [new()]; + class Program + { + static void Main() + { + IDictionary d = [P:P]; + } + static object P { set { } } + } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (2,20): error CS1729: 'string' does not contain a constructor that takes 0 arguments - // IList z = [new()]; - Diagnostic(ErrorCode.ERR_BadCtorArgCount, "new()").WithArguments("string", "0").WithLocation(2, 20)); + // (6,42): error CS0154: The property or indexer 'Program.P' cannot be used in this context because it lacks the get accessor + // IDictionary d = [P:P]; + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "P").WithArguments("Program.P").WithLocation(6, 42), + // (6,44): error CS0154: The property or indexer 'Program.P' cannot be used in this context because it lacks the get accessor + // IDictionary d = [P:P]; + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "P").WithArguments("Program.P").WithLocation(6, 44)); } [Fact] From 5adcda653ab6a8060d73978c89bc4a3f5ff95a61 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:28:01 -0800 Subject: [PATCH 11/16] Address feedback --- .../LocalRewriter_CollectionExpression.cs | 5 +- .../Semantics/DictionaryExpressionTests.cs | 278 ++++++++++++++++-- .../Core/Portable/WellKnownMembers.cs | 8 +- 3 files changed, 265 insertions(+), 26 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 2dbde0fe7b8eb..da6eae451f119 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -1271,8 +1271,9 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no sideEffects.Add(assignmentToTemp); var getKeyMethod = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key)).AsMember(sourceType); var getValueMethod = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value)).AsMember(sourceType); - return (_factory.Convert(addMethod.Parameters[0].Type, _factory.Call(expressionTemp, getKeyMethod)), - _factory.Convert(addMethod.Parameters[1].Type, _factory.Call(expressionTemp, getValueMethod))); + Debug.Assert(ConversionsBase.HasIdentityConversion(getKeyMethod.ReturnType, addMethod.Parameters[0].Type)); + Debug.Assert(ConversionsBase.HasIdentityConversion(getValueMethod.ReturnType, addMethod.Parameters[1].Type)); + return (_factory.Call(expressionTemp, getKeyMethod), _factory.Call(expressionTemp, getValueMethod)); } private BoundExpression RewriteCollectionExpressionElementExpression(BoundNode element) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 3ef2877c9aef3..aa5e9d75d8b08 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -2,7 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable disable + +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -10,6 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class DictionaryExpressionTests : CSharpTestBase { + private const string s_collectionExtensions = CollectionExpressionTests.s_collectionExtensions; private const string s_dictionaryExtensions = """ using System; using System.Collections.Generic; @@ -33,15 +39,16 @@ internal static void Report(this IEnumerable> e) builder.Append(":"); Append(builder, kvp.Value); } - builder.Append("]"); + builder.Append("], "); Console.Write(builder.ToString()); } } """; + public static readonly TheoryData LanguageVersions = new([LanguageVersion.CSharp13, LanguageVersion.Preview, LanguageVersionFacts.CSharpNext]); + [Theory] - [InlineData(LanguageVersion.CSharp13)] - [InlineData(LanguageVersion.Preview)] + [MemberData(nameof(LanguageVersions))] public void LanguageVersionDiagnostics_01(LanguageVersion languageVersion) { string source = """ @@ -66,8 +73,7 @@ public void LanguageVersionDiagnostics_01(LanguageVersion languageVersion) } [Theory] - [InlineData(LanguageVersion.CSharp13)] - [InlineData(LanguageVersion.Preview)] + [MemberData(nameof(LanguageVersions))] public void LanguageVersionDiagnostics_02(LanguageVersion languageVersion) { string source = """ @@ -128,10 +134,43 @@ static Dictionary F(KeyValuePair x, IEnumerable").WithLocation(12, 17)); } + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void DictionaryInterface_01(string typeName) + { + string source = $$""" + using System.Collections.Generic; + class Program + { + static void Main() + { + F().Report(); + } + static {{typeName}} F() + { + return []; + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: "[], "); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.F", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: newobj "System.Collections.Generic.Dictionary..ctor()" + IL_0005: ret + } + """); + } + [Theory] [InlineData("IDictionary")] [InlineData("IReadOnlyDictionary")] - public void DictionaryInterface(string typeName) + public void DictionaryInterface_02(string typeName) { string source = $$""" using System.Collections.Generic; @@ -151,7 +190,7 @@ static void Main() """; var verifier = CompileAndVerify( [source, s_dictionaryExtensions], - expectedOutput: "[1:one, 2:two, 3:three]"); + expectedOutput: "[1:one, 2:two, 3:three], "); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.F", """ { @@ -305,6 +344,22 @@ public void KeyValuePair_MissingMember(string typeName) var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics(); + comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_KeyValuePair_KV); + comp.VerifyEmitDiagnostics( + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(3, 5), + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = []; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Key' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Key").WithLocation(4, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.KeyValuePair`2.get_Value' + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.KeyValuePair`2", "get_Value").WithLocation(4, 5)); + comp = CreateCompilation(source); comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key); comp.VerifyEmitDiagnostics( @@ -339,7 +394,39 @@ public void KeyValuePair_MissingMember(string typeName) } [Fact] - public void KeyValueConversions() + public void KeyValuePairConversions_01() + { + string source = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + F(1, "one").Report(); + } + static IDictionary F(int x, string y) + { + return [x:y]; + } + } + """; + var comp = CreateCompilation([source, s_dictionaryExtensions], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "[1:one], "); + verifier.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var kvpElement = tree.GetRoot().DescendantNodes().OfType().Single(); + var typeInfo = model.GetTypeInfo(kvpElement.KeyExpression); + Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); + Assert.Equal(SpecialType.System_Int64, typeInfo.ConvertedType.SpecialType); + typeInfo = model.GetTypeInfo(kvpElement.ValueExpression); + Assert.Equal(SpecialType.System_String, typeInfo.Type.SpecialType); + Assert.Equal(SpecialType.System_Object, typeInfo.ConvertedType.SpecialType); + } + + [Fact] + public void KeyValuePairConversions_02() { string source = """ using System.Collections.Generic; @@ -367,6 +454,119 @@ static IDictionary F(KeyValuePair x, IEnumerable", "System.Collections.Generic.KeyValuePair").WithLocation(12, 22)); } + [Fact] + public void KeyValuePairConversions_03() + { + string sourceA = """ + using System.Collections.Generic; + public class MyKeyValuePair + { + public MyKeyValuePair(K key, V value) + { + Key = key; + Value = value; + } + public readonly K Key; + public readonly V Value; + public override string ToString() => $"{Key}:{Value}"; + public static implicit operator MyKeyValuePair(KeyValuePair kvp) => new(kvp.Key, kvp.Value); + } + """; + var comp = CreateCompilation(sourceA); + var refA = comp.EmitToImageReference(); + + string sourceB1 = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + F(1, "one"); + } + static IEnumerable> F(K k, V v) + { + return [k:v]; + } + } + """; + comp = CreateCompilation(sourceB1, references: [refA]); + comp.VerifyEmitDiagnostics( + // (10,17): error CS9268: Collection expression type 'IEnumerable>' does not support key-value pair elements. + // return [k:v]; + Diagnostic(ErrorCode.ERR_CollectionExpressionKeyValuePairNotSupported, "k:v").WithArguments("System.Collections.Generic.IEnumerable>").WithLocation(10, 17)); + + string sourceB2 = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + var e = F(new KeyValuePair(2, "two"), new KeyValuePair[] { new(3, "three") }); + e.Report(); + } + static IEnumerable> F(KeyValuePair x, IEnumerable> y) + { + return [x, ..y]; + } + } + """; + var verifier = CompileAndVerify( + [sourceB2, s_collectionExtensions], + references: [refA], + expectedOutput: "[2:two, 3:three], "); + verifier.VerifyDiagnostics(); + } + + [Fact] + public void KeyValuePairConversions_04() + { + string sourceA = """ + using System.Collections.Generic; + public class MyKeyValuePair + { + public MyKeyValuePair(K key, V value) + { + Key = key; + Value = value; + } + public readonly K Key; + public readonly V Value; + public static implicit operator KeyValuePair(MyKeyValuePair kvp) => new(kvp.Key, kvp.Value); + } + """; + var comp = CreateCompilation(sourceA); + var refA = comp.EmitToImageReference(); + + string sourceB = """ + using System.Collections.Generic; + class Program + { + static void Main() + { + var x = new MyKeyValuePair(2, "two"); + var y = new MyKeyValuePair[] { new(3, "three") }; + var e = F1(x, y); + e.Report(); + var d = F2(x, y); + d.Report(); + } + static IEnumerable> F1(MyKeyValuePair x, IEnumerable> y) + { + return [x, ..y]; + } + static IDictionary F2(MyKeyValuePair x, IEnumerable> y) + { + return [x, ..y]; + } + } + """; + var verifier = CompileAndVerify( + [sourceB, s_collectionExtensions], + references: [refA], + expectedOutput: "[[2, two], [3, three]], [[2, two], [3, three]], "); + verifier.VerifyDiagnostics(); + } + [Fact] public void EvaluationOrder_01() { @@ -397,7 +597,7 @@ static T Identity(T value) 2 two System.Collections.Generic.KeyValuePair`2[System.Int32,System.String][] - [1:one, 2:two, 3:three] + [1:one, 2:two, 3:three], """); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ @@ -472,10 +672,26 @@ public void InferredType_ExpressionElement() { string source = """ using System.Collections.Generic; - IDictionary d = [default, new()]; + IDictionary d = [new()]; + d.Report(); + d = [default]; + d.Report(); """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics(); + var comp = CreateCompilation([source, s_dictionaryExtensions], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "[0:null], [0:null], "); + verifier.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var elements = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + var typeInfo = model.GetTypeInfo(elements[0].Expression); + Assert.Equal("System.Collections.Generic.KeyValuePair", typeInfo.Type.ToTestDisplayString()); + Assert.Equal("System.Collections.Generic.KeyValuePair", typeInfo.ConvertedType.ToTestDisplayString()); + + typeInfo = model.GetTypeInfo(elements[1].Expression); + Assert.Equal("System.Collections.Generic.KeyValuePair", typeInfo.Type.ToTestDisplayString()); + Assert.Equal("System.Collections.Generic.KeyValuePair", typeInfo.ConvertedType.ToTestDisplayString()); } [Fact] @@ -483,10 +699,32 @@ public void InferredType_KeyValueElement() { string source = """ using System.Collections.Generic; - IDictionary d = [default:default, null:new()]; + IDictionary d = [default:default]; + d.Report(); + d = [new():null]; + d.Report(); """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics(); + var comp = CreateCompilation([source, s_dictionaryExtensions], options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: "[0:null], [0:null], "); + verifier.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var elements = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + var typeInfo = model.GetTypeInfo(elements[0].KeyExpression); + Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); + Assert.Equal(SpecialType.System_Int32, typeInfo.ConvertedType.SpecialType); + typeInfo = model.GetTypeInfo(elements[0].ValueExpression); + Assert.Equal(SpecialType.System_String, typeInfo.Type.SpecialType); + Assert.Equal(SpecialType.System_String, typeInfo.ConvertedType.SpecialType); + + typeInfo = model.GetTypeInfo(elements[1].KeyExpression); + Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); + Assert.Equal(SpecialType.System_Int32, typeInfo.ConvertedType.SpecialType); + typeInfo = model.GetTypeInfo(elements[1].ValueExpression); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_String, typeInfo.ConvertedType.SpecialType); } [Fact] @@ -596,7 +834,7 @@ static void Main() var x = new Lock(); var y = new Lock(); object[] a = [x]; - IDictionary d = [x:1, 2:y]; + IDictionary d = [x:y]; } } """; @@ -606,11 +844,11 @@ static void Main() // object[] a = [x]; Diagnostic(ErrorCode.WRN_ConvertingLock, "x").WithLocation(9, 23), // (10,42): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - // IDictionary d = [x:1, 2:y]; + // IDictionary d = [x:y]; Diagnostic(ErrorCode.WRN_ConvertingLock, "x").WithLocation(10, 42), - // (10,49): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. - // IDictionary d = [x:1, 2:y]; - Diagnostic(ErrorCode.WRN_ConvertingLock, "y").WithLocation(10, 49)); + // (10,44): warning CS9216: A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement. + // IDictionary d = [x:y]; + Diagnostic(ErrorCode.WRN_ConvertingLock, "y").WithLocation(10, 44)); } } } diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 8cf805e93f18e..d2c1d59b47c6e 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -5172,14 +5172,14 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Reflection_MethodInfo, // System_Collections_Generic_Dictionary_KV__ctor - (byte)MemberFlags.Constructor, // Flags + (byte)MemberFlags.Constructor, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_Dictionary_KV - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // System_Collections_Generic_Dictionary_KV__Add - (byte)MemberFlags.Method, // Flags + (byte)MemberFlags.Method, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_Dictionary_KV - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 2, // Method Signature @@ -5188,14 +5188,14 @@ static WellKnownMembers() (byte)SignatureTypeCode.GenericTypeParameter, 1, // System_Collections_Generic_KeyValuePair_KV__get_Key - (byte)MemberFlags.PropertyGet, // Flags + (byte)MemberFlags.PropertyGet, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_KeyValuePair_KV - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 0, // Method Signature (byte)SignatureTypeCode.GenericTypeParameter, 0, // Return Type // System_Collections_Generic_KeyValuePair_KV__get_Value - (byte)MemberFlags.PropertyGet, // Flags + (byte)MemberFlags.PropertyGet, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_KeyValuePair_KV - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 0, // Method Signature From 0b27591d5ef419082b9180c791221112a6a173d9 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:19:35 -0800 Subject: [PATCH 12/16] Verify Dictionary<,> implements dictionary interface --- .../Portable/Binder/Binder_Conversions.cs | 10 ++ .../Semantics/DictionaryExpressionTests.cs | 116 +++++++++++++++++- 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 32f3bab7f41fc..a9136a89a5591 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -937,6 +937,16 @@ private BoundCollectionExpression ConvertCollectionExpression( // gives a consistent behavior, regardless of collection expression elements. if (collectionTypeKind is CollectionExpressionTypeKind.DictionaryInterface) { + var dictionaryType = GetWellKnownType(WellKnownType.System_Collections_Generic_Dictionary_KV, diagnostics, syntax). + Construct(((NamedTypeSymbol)targetType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics); + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + var dictionaryConversion = Conversions.ClassifyConversionFromType(dictionaryType, targetType, isChecked: false, ref useSiteInfo); + diagnostics.Add(syntax, useSiteInfo); + if (!dictionaryConversion.IsImplicit) + { + GenerateImplicitConversionError(diagnostics, Compilation, syntax, dictionaryConversion, dictionaryType, targetType); + } + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key, diagnostics, syntax: syntax); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index aa5e9d75d8b08..290b742a915f7 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -135,8 +135,8 @@ static Dictionary F(KeyValuePair x, IEnumerable")] - [InlineData("IReadOnlyDictionary")] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] public void DictionaryInterface_01(string typeName) { string source = $$""" @@ -147,7 +147,7 @@ static void Main() { F().Report(); } - static {{typeName}} F() + static {{typeName}} F() { return []; } @@ -168,8 +168,8 @@ .maxstack 1 } [Theory] - [InlineData("IDictionary")] - [InlineData("IReadOnlyDictionary")] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] public void DictionaryInterface_02(string typeName) { string source = $$""" @@ -182,7 +182,7 @@ static void Main() var y = new KeyValuePair[] { new(3, "three") }; F(x, y).Report(); } - static {{typeName}} F(KeyValuePair x, IEnumerable> y) + static {{typeName}} F(KeyValuePair x, IEnumerable> y) { return [1:"one", x, ..y]; } @@ -248,6 +248,98 @@ .locals init (System.Collections.Generic.Dictionary V_0, """); } + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void DictionaryNotImplementingIDictionary(string typeName) + { + string sourceA = """ + namespace System + { + public class Object { } + public abstract class ValueType { } + public class String { } + public class Type { } + public struct Void { } + public struct Boolean { } + public struct Int32 { } + public struct Enum { } + public class Attribute { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets t) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + public enum AttributeTargets { } + public interface IDisposable + { + void Dispose(); + } + } + namespace System.Collections + { + public interface IEnumerator + { + bool MoveNext(); + object Current { get; } + } + public interface IEnumerable + { + IEnumerator GetEnumerator(); + } + } + namespace System.Collections.Generic + { + public interface IEnumerator : IEnumerator + { + new T Current { get; } + } + public interface IEnumerable : IEnumerable + { + new IEnumerator GetEnumerator(); + } + public interface IDictionary : IEnumerable> + { + } + public interface IReadOnlyDictionary : IEnumerable> + { + } + public struct KeyValuePair + { + public TKey Key { get; } + public TValue Value { get; } + } + public sealed class Dictionary : IEnumerable> + { + public Dictionary() { } + public void Add(TKey key, TValue value) { } + IEnumerator> IEnumerable>.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + } + } + """; + string sourceB = $$""" + using System.Collections.Generic; + class Program + { + static void Main() + { + var d = F(1, "one", new KeyValuePair(), null); + } + static {{typeName}} F(K k, V v, KeyValuePair x, IEnumerable> y) + { + return [k:v, x, ..y]; + } + } + """; + var comp = CreateEmptyCompilation(new[] { sourceA, sourceB }); + comp.VerifyEmitDiagnostics(Microsoft.CodeAnalysis.Emit.EmitOptions.Default.WithRuntimeMetadataVersion("0.0.0.0"), + // 1.cs(10,16): error CS0029: Cannot implicitly convert type 'System.Collections.Generic.Dictionary' to 'System.Collections.Generic.IDictionary' + // return [k:v, x, ..y]; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "[k:v, x, ..y]").WithArguments("System.Collections.Generic.Dictionary", $"System.Collections.Generic.{typeName}").WithLocation(10, 16)); + } + [Theory] [InlineData("IDictionary")] [InlineData("IReadOnlyDictionary")] @@ -269,24 +361,36 @@ public void DictionaryInterface_MissingMember(string typeName) comp = CreateCompilation(source); comp.MakeTypeMissing(WellKnownType.System_Collections_Generic_Dictionary_KV); comp.VerifyEmitDiagnostics( + // (3,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported + // d = []; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(3, 5), // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = []; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(3, 5), // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' // d = []; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(3, 5), + // (4,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported + // d = [1:"one"]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(4, 5), // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [1:"one"]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(4, 5), // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' // d = [1:"one"]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(4, 5), + // (5,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported + // d = [new KeyValuePair(2, "two")]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(5, 5), // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [new KeyValuePair(2, "two")]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(5, 5), // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' // d = [new KeyValuePair(2, "two")]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(5, 5), + // (6,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported + // d = [.. new KeyValuePair[] { new(3, "three") }]; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(6, 5), // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [.. new KeyValuePair[] { new(3, "three") }]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(6, 5), From db8a75df319f9e436c087681c19a5532e98defe5 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:36:08 -0800 Subject: [PATCH 13/16] Use indexer --- .../Portable/Binder/Binder_Conversions.cs | 2 +- .../LocalRewriter_CollectionExpression.cs | 12 +- .../Symbols/Compilation_WellKnownMembers.cs | 5 + .../Semantics/DictionaryExpressionTests.cs | 117 ++++++++++++++---- .../Core/Portable/MemberDescriptor.cs | 9 +- .../Core/Portable/WellKnownMember.cs | 2 +- .../Core/Portable/WellKnownMembers.cs | 6 +- 7 files changed, 114 insertions(+), 39 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index a9136a89a5591..fbd0eb33dc51e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -948,7 +948,7 @@ private BoundCollectionExpression ConvertCollectionExpression( } _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor, diagnostics, syntax: syntax); - _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add, diagnostics, syntax: syntax); + _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_Dictionary_KV__set_Item, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Key, diagnostics, syntax: syntax); _ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_KeyValuePair_KV__get_Value, diagnostics, syntax: syntax); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index da6eae451f119..eb3472f9d0cbf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -1198,7 +1198,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no localsBuilder.Add(dictionaryTemp); sideEffects.Add(assignmentToTemp); - var addMethod = _factory.WellKnownMethod(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add).AsMember(collectionType); + var setItemMethod = _factory.WellKnownMethod(WellKnownMember.System_Collections_Generic_Dictionary_KV__set_Item).AsMember(collectionType); for (int i = 0; i < elements.Length; i++) { var element = elements[i]; @@ -1208,7 +1208,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no { var rewrittenKey = VisitExpression(keyValuePairElement.Key); var rewrittenValue = VisitExpression(keyValuePairElement.Value); - sideEffects.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); + sideEffects.Add(_factory.Call(dictionaryTemp, setItemMethod, rewrittenKey, rewrittenValue)); } break; case BoundCollectionExpressionSpreadElement spreadElement: @@ -1220,8 +1220,8 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no { var expression = ((BoundExpressionStatement)iteratorBody).Expression; var builder = ArrayBuilder.GetInstance(); - var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair(expression, addMethod, builder, localsBuilder); - builder.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); + var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair(expression, setItemMethod, builder, localsBuilder); + builder.Add(_factory.Call(dictionaryTemp, setItemMethod, rewrittenKey, rewrittenValue)); var statements = builder.SelectAsArray(expr => (BoundStatement)new BoundExpressionStatement(expr.Syntax, expr)); builder.Free(); Debug.Assert(statements.Length > 0); @@ -1233,8 +1233,8 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no break; default: { - var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair((BoundExpression)element, addMethod, sideEffects, localsBuilder); - sideEffects.Add(_factory.Call(dictionaryTemp, addMethod, rewrittenKey, rewrittenValue)); + var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair((BoundExpression)element, setItemMethod, sideEffects, localsBuilder); + sideEffects.Add(_factory.Call(dictionaryTemp, setItemMethod, rewrittenKey, rewrittenValue)); } break; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs index 65292086a7814..ead0210310277 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs @@ -273,6 +273,11 @@ internal override ITypeSymbolInternal CommonGetWellKnownType(WellKnownType wellk targetMethodKind = MethodKind.PropertyGet; break; + case MemberFlags.PropertySet: + targetSymbolKind = SymbolKind.Method; + targetMethodKind = MethodKind.PropertySet; + break; + case MemberFlags.Field: targetSymbolKind = SymbolKind.Field; break; diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 290b742a915f7..98ac1d0e3a38f 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -19,6 +19,7 @@ public class DictionaryExpressionTests : CSharpTestBase private const string s_dictionaryExtensions = """ using System; using System.Collections.Generic; + using System.Linq; using System.Text; static class DictionaryExtensions { @@ -28,6 +29,7 @@ private static void Append(StringBuilder builder, object value) } internal static void Report(this IEnumerable> e) { + e = e.OrderBy(kvp => kvp.Key); var builder = new StringBuilder(); builder.Append("["); bool isFirst = true; @@ -205,7 +207,7 @@ .locals init (System.Collections.Generic.Dictionary V_0, IL_0006: ldloc.0 IL_0007: ldc.i4.1 IL_0008: ldstr "one" - IL_000d: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_000d: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_0012: ldarg.0 IL_0013: stloc.1 IL_0014: ldloc.0 @@ -213,7 +215,7 @@ .locals init (System.Collections.Generic.Dictionary V_0, IL_0017: call "int System.Collections.Generic.KeyValuePair.Key.get" IL_001c: ldloca.s V_1 IL_001e: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_0023: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0023: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_0028: ldarg.1 IL_0029: callvirt "System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator()" IL_002e: stloc.3 @@ -228,7 +230,7 @@ .locals init (System.Collections.Generic.Dictionary V_0, IL_003b: call "int System.Collections.Generic.KeyValuePair.Key.get" IL_0040: ldloca.s V_2 IL_0042: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_0047: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0047: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_004c: ldloc.3 IL_004d: callvirt "bool System.Collections.IEnumerator.MoveNext()" IL_0052: brtrue.s IL_0031 @@ -248,6 +250,66 @@ .locals init (System.Collections.Generic.Dictionary V_0, """); } + [Theory] + [InlineData("IDictionary")] + [InlineData("IReadOnlyDictionary")] + public void DictionaryInterface_DuplicateKeys(string typeName) + { + string source = $$""" + using System; + using System.Collections.Generic; + class Program + { + static void Main() + { + var x = new KeyValuePair(101, "one"); + var y = new KeyValuePair(202, "two"); + var z = new KeyValuePair(101, "three"); + Report(F1(x, y, z)); + Report(F1(y, z, x)); + Report(F1(z, x, y)); + Report(F2(x, y, z)); + Report(F2(y, z, x)); + Report(F2(z, x, y)); + Report(F3(x, y, z)); + Report(F3(y, z, x)); + Report(F3(z, x, y)); + } + static void Report({{typeName}} d) + { + d.Report(); + Console.WriteLine(); + } + static {{typeName}} F1(KeyValuePair x, KeyValuePair y, KeyValuePair z) + { + return [x.Key:x.Value, y, .. new[] { z }]; + } + static {{typeName}} F2(KeyValuePair x, KeyValuePair y, KeyValuePair z) + { + return [x, .. new[] { y }, z.Key:z.Value]; + } + static {{typeName}} F3(KeyValuePair x, KeyValuePair y, KeyValuePair z) + { + return [.. new[] { x }, y.Key:y.Value, z]; + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: """ + [101:three, 202:two], + [101:one, 202:two], + [101:one, 202:two], + [101:three, 202:two], + [101:one, 202:two], + [101:one, 202:two], + [101:three, 202:two], + [101:one, 202:two], + [101:one, 202:two], + """); + verifier.VerifyDiagnostics(); + } + [Theory] [InlineData("IDictionary")] [InlineData("IReadOnlyDictionary")] @@ -313,11 +375,18 @@ public struct KeyValuePair public sealed class Dictionary : IEnumerable> { public Dictionary() { } - public void Add(TKey key, TValue value) { } + public TValue this[TKey key] { get { return default; } set { } } IEnumerator> IEnumerable>.GetEnumerator() => null; IEnumerator IEnumerable.GetEnumerator() => null; } } + namespace System.Reflection + { + public class DefaultMemberAttribute : Attribute + { + public DefaultMemberAttribute(string name) { } + } + } """; string sourceB = $$""" using System.Collections.Generic; @@ -367,36 +436,36 @@ public void DictionaryInterface_MissingMember(string typeName) // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = []; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(3, 5), - // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = []; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(3, 5), + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(3, 5), // (4,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported // d = [1:"one"]; Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(4, 5), // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [1:"one"]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(4, 5), - // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [1:"one"]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(4, 5), + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(4, 5), // (5,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported // d = [new KeyValuePair(2, "two")]; Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(5, 5), // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [new KeyValuePair(2, "two")]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(5, 5), - // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [new KeyValuePair(2, "two")]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(5, 5), + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(5, 5), // (6,5): error CS0518: Predefined type 'System.Collections.Generic.Dictionary`2' is not defined or imported // d = [.. new KeyValuePair[] { new(3, "three") }]; Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2").WithLocation(6, 5), // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2..ctor' // d = [.. new KeyValuePair[] { new(3, "three") }]; Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(6, 5), - // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [.. new KeyValuePair[] { new(3, "three") }]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(6, 5)); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(6, 5)); comp = CreateCompilation(source); comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_Dictionary_KV__ctor); @@ -415,20 +484,20 @@ public void DictionaryInterface_MissingMember(string typeName) Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", ".ctor").WithLocation(6, 5)); comp = CreateCompilation(source); - comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_Dictionary_KV__Add); + comp.MakeMemberMissing(WellKnownMember.System_Collections_Generic_Dictionary_KV__set_Item); comp.VerifyEmitDiagnostics( - // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + // (3,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = []; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(3, 5), - // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "[]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(3, 5), + // (4,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [1:"one"]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(4, 5), - // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[1:""one""]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(4, 5), + // (5,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [new KeyValuePair(2, "two")]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(5, 5), - // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.Add' + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[new KeyValuePair(2, ""two"")]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(5, 5), + // (6,5): error CS0656: Missing compiler required member 'System.Collections.Generic.Dictionary`2.set_Item' // d = [.. new KeyValuePair[] { new(3, "three") }]; - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "Add").WithLocation(6, 5)); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"[.. new KeyValuePair[] { new(3, ""three"") }]").WithArguments("System.Collections.Generic.Dictionary`2", "set_Item").WithLocation(6, 5)); } [Theory] @@ -725,13 +794,13 @@ .locals init (System.Collections.Generic.Dictionary V_0, IL_001a: call "int System.Collections.Generic.KeyValuePair.Key.get" IL_001f: ldloca.s V_1 IL_0021: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_0026: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_0026: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_002b: ldloc.0 IL_002c: ldc.i4.2 IL_002d: call "int Program.Identity(int)" IL_0032: ldstr "two" IL_0037: call "string Program.Identity(string)" - IL_003c: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_003c: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_0041: ldc.i4.1 IL_0042: newarr "System.Collections.Generic.KeyValuePair" IL_0047: dup @@ -754,7 +823,7 @@ .locals init (System.Collections.Generic.Dictionary V_0, IL_0070: call "int System.Collections.Generic.KeyValuePair.Key.get" IL_0075: ldloca.s V_2 IL_0077: call "string System.Collections.Generic.KeyValuePair.Value.get" - IL_007c: callvirt "void System.Collections.Generic.Dictionary.Add(int, string)" + IL_007c: callvirt "void System.Collections.Generic.Dictionary.this[int].set" IL_0081: ldloc.s V_4 IL_0083: ldc.i4.1 IL_0084: add diff --git a/src/Compilers/Core/Portable/MemberDescriptor.cs b/src/Compilers/Core/Portable/MemberDescriptor.cs index b9d932f072818..3bcc3226bbaf7 100644 --- a/src/Compilers/Core/Portable/MemberDescriptor.cs +++ b/src/Compilers/Core/Portable/MemberDescriptor.cs @@ -19,13 +19,14 @@ internal enum MemberFlags : byte Field = 0x02, Constructor = 0x04, PropertyGet = 0x08, - Property = 0x10, + PropertySet = 0x10, + Property = 0x20, // END Mutually exclusive Member kinds - KindMask = 0x1F, + KindMask = 0x3F, - Static = 0x20, - Virtual = 0x40, // Virtual in CLR terms, i.e. sealed should be accepted. + Static = 0x40, + Virtual = 0x80, // Virtual in CLR terms, i.e. sealed should be accepted. } /// diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index d9519b9023f22..9aefe41bb9dfb 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -715,7 +715,7 @@ internal enum WellKnownMember System_Linq_Expressions_Expression__Power_MethodInfo, System_Collections_Generic_Dictionary_KV__ctor, - System_Collections_Generic_Dictionary_KV__Add, + System_Collections_Generic_Dictionary_KV__set_Item, System_Collections_Generic_KeyValuePair_KV__get_Key, System_Collections_Generic_KeyValuePair_KV__get_Value, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index d2c1d59b47c6e..c57d2e2415439 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -5178,8 +5178,8 @@ static WellKnownMembers() 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - // System_Collections_Generic_Dictionary_KV__Add - (byte)MemberFlags.Method, // Flags + // System_Collections_Generic_Dictionary_KV__set_Item + (byte)MemberFlags.PropertySet, // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_Dictionary_KV - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 2, // Method Signature @@ -5824,7 +5824,7 @@ static WellKnownMembers() "Default", // System_Linq_Expressions_Expression__Default "Power", // System_Linq_Expressions_Expression__Power_MethodInfo, ".ctor", // System_Collections_Generic_Dictionary_KV__ctor, - "Add", // System_Collections_Generic_Dictionary_KV__Add + "set_Item", // System_Collections_Generic_Dictionary_KV__set_Item "get_Key", // System_Collections_Generic_KeyValuePair_KV__get_Key "get_Value", // System_Collections_Generic_KeyValuePair_KV__get_Value }; From 149ec03d51107bf60ace19c9bbd8b47f0c3d2406 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:00:31 -0800 Subject: [PATCH 14/16] Fix unit test --- .../VisualBasic/Portable/Symbols/WellKnownMembers.vb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb b/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb index 4e898d90e43c9..e0f679f3e68d2 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/WellKnownMembers.vb @@ -442,6 +442,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic targetSymbolKind = SymbolKind.Method targetMethodKind = MethodKind.PropertyGet + Case MemberFlags.PropertySet + targetSymbolKind = SymbolKind.Method + targetMethodKind = MethodKind.PropertySet + Case MemberFlags.Field targetSymbolKind = SymbolKind.Field From a03b3235c067675116fb4f32feb89ad2ba193ae7 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:10:09 -0800 Subject: [PATCH 15/16] Update comment --- .../Portable/Binder/Semantics/Conversions/Conversions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs index 39fd13300ab76..6930997f56579 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs @@ -259,8 +259,11 @@ internal Conversion GetCollectionExpressionSpreadElementConversion( { return Conversion.NoConversion; } - // This should be conversion from type rather than conversion - // from expression. The difference is in handling of dynamic. + // This should be conversion from type rather than conversion from expression. + // The difference is in handling of dynamic, and fixing this would be a breaking + // change for that case. For instance, the following would become an error: + // dynamic[] x = [1, 2, 3]; + // int[] y = [.. x]; return ClassifyImplicitConversionFromExpression( new BoundValuePlaceholder(element.Syntax, enumeratorInfo.ElementType), targetType, From f45f29cd66a06f596b767537e2e8635149bf95da Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:27:05 -0800 Subject: [PATCH 16/16] Address feedback --- .../LocalRewriter_CollectionExpression.cs | 3 + .../Semantics/DictionaryExpressionTests.cs | 138 +++++++++++++++++- 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index eb3472f9d0cbf..9990dc882e3cc 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -1206,6 +1206,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no { case BoundKeyValuePairElement keyValuePairElement: { + // dictionary[key] = value; var rewrittenKey = VisitExpression(keyValuePairElement.Key); var rewrittenValue = VisitExpression(keyValuePairElement.Value); sideEffects.Add(_factory.Call(dictionaryTemp, setItemMethod, rewrittenKey, rewrittenValue)); @@ -1218,6 +1219,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no rewrittenExpression, iteratorBody => { + // dictionary[item.Key] = item.Value; var expression = ((BoundExpressionStatement)iteratorBody).Expression; var builder = ArrayBuilder.GetInstance(); var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair(expression, setItemMethod, builder, localsBuilder); @@ -1233,6 +1235,7 @@ private BoundExpression CreateAndPopulateDictionary(BoundCollectionExpression no break; default: { + // dictionary[element.Key] = element.Value; var (rewrittenKey, rewrittenValue) = RewriteKeyValuePair((BoundExpression)element, setItemMethod, sideEffects, localsBuilder); sideEffects.Add(_factory.Call(dictionaryTemp, setItemMethod, rewrittenKey, rewrittenValue)); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs index 98ac1d0e3a38f..648c537357825 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/DictionaryExpressionTests.cs @@ -107,6 +107,33 @@ public void LanguageVersionDiagnostics_02(LanguageVersion languageVersion) } } + [Theory] + [MemberData(nameof(LanguageVersions))] + public void LanguageVersionDiagnostics_03(LanguageVersion languageVersion) + { + string source = """ + var x = [1:"one"]; + """; + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (1,9): error CS9176: There is no target type for the collection expression. + // var x = [1:"one"]; + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, @"[1:""one""]").WithLocation(1, 9), + // (1,11): error CS8652: The feature 'dictionary expressions' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var x = [1:"one"]; + Diagnostic(ErrorCode.ERR_FeatureInPreview, ":").WithArguments("dictionary expressions").WithLocation(1, 11)); + } + else + { + comp.VerifyEmitDiagnostics( + // (1,9): error CS9176: There is no target type for the collection expression. + // var x = [1:"one"]; + Diagnostic(ErrorCode.ERR_CollectionExpressionNoTargetType, @"[1:""one""]").WithLocation(1, 9)); + } + } + [Fact] public void Dictionary() { @@ -579,7 +606,7 @@ static void Main() } static IDictionary F(int x, string y) { - return [x:y]; + return /**/[x:y]/**/; } } """; @@ -596,6 +623,14 @@ static IDictionary F(int x, string y) typeInfo = model.GetTypeInfo(kvpElement.ValueExpression); Assert.Equal(SpecialType.System_String, typeInfo.Type.SpecialType); Assert.Equal(SpecialType.System_Object, typeInfo.ConvertedType.SpecialType); + + // PROTOTYPE: Implement IOperation support. + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Collections.Generic.IDictionary) (Syntax: '[x:y]') + Elements(1): + IOperation: (OperationKind.None, Type: null) (Syntax: 'x:y') + """); } [Fact] @@ -840,6 +875,107 @@ .locals init (System.Collections.Generic.Dictionary V_0, """); } + [Fact] + public void EvaluationOrder_02() + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void Main() + { + var d = F(101, "A", 102, "B", 103, "C", true, 3); + d.Report(); + } + static IReadOnlyDictionary F(K k1, V v1, K k2, V v2, K k3, V v3, bool b, int i) + { + return [ + Identity(Identity(b) ? Identity(k1) : Identity(k2)) : Identity(v2), + Identity(k3) : Identity(Identity(i) switch { 1 => Identity(v1), 2 => Identity(v2), _ => Identity(v3) })]; + } + static T Identity(T value) + { + Console.WriteLine(value); + return value; + } + } + """; + var verifier = CompileAndVerify( + [source, s_dictionaryExtensions], + expectedOutput: """ + True + 101 + 101 + B + 103 + 3 + C + C + [101:B, 103:C], + """); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.F", """ + { + // Code size 119 (0x77) + .maxstack 3 + .locals init (System.Collections.Generic.Dictionary V_0, + K V_1, + V V_2, + int V_3, + System.Collections.Generic.Dictionary V_4) + IL_0000: newobj "System.Collections.Generic.Dictionary..ctor()" + IL_0005: stloc.s V_4 + IL_0007: ldloc.s V_4 + IL_0009: ldarg.s V_6 + IL_000b: call "bool Program.Identity(bool)" + IL_0010: brtrue.s IL_001a + IL_0012: ldarg.2 + IL_0013: call "K Program.Identity(K)" + IL_0018: br.s IL_0020 + IL_001a: ldarg.0 + IL_001b: call "K Program.Identity(K)" + IL_0020: call "K Program.Identity(K)" + IL_0025: ldarg.3 + IL_0026: call "V Program.Identity(V)" + IL_002b: callvirt "void System.Collections.Generic.Dictionary.this[K].set" + IL_0030: ldloc.s V_4 + IL_0032: stloc.0 + IL_0033: ldarg.s V_4 + IL_0035: call "K Program.Identity(K)" + IL_003a: stloc.1 + IL_003b: ldarg.s V_7 + IL_003d: call "int Program.Identity(int)" + IL_0042: stloc.3 + IL_0043: ldloc.3 + IL_0044: ldc.i4.1 + IL_0045: beq.s IL_004d + IL_0047: ldloc.3 + IL_0048: ldc.i4.2 + IL_0049: beq.s IL_0056 + IL_004b: br.s IL_005f + IL_004d: ldarg.1 + IL_004e: call "V Program.Identity(V)" + IL_0053: stloc.2 + IL_0054: br.s IL_0067 + IL_0056: ldarg.3 + IL_0057: call "V Program.Identity(V)" + IL_005c: stloc.2 + IL_005d: br.s IL_0067 + IL_005f: ldarg.s V_5 + IL_0061: call "V Program.Identity(V)" + IL_0066: stloc.2 + IL_0067: ldloc.0 + IL_0068: ldloc.1 + IL_0069: ldloc.2 + IL_006a: call "V Program.Identity(V)" + IL_006f: callvirt "void System.Collections.Generic.Dictionary.this[K].set" + IL_0074: ldloc.s V_4 + IL_0076: ret + } + """); + } + [Fact] public void InferredType_ExpressionElement() {