File: Binder\Binder_AnonymousTypes.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// This portion of the binder converts a AnonymousObjectCreationExpressionSyntax into 
    /// a bound anonymous object creation node
    /// </summary>
    internal partial class Binder
    {
        private BoundExpression BindAnonymousObjectCreation(AnonymousObjectCreationExpressionSyntax node, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureAnonymousTypes.CheckFeatureAvailability(diagnostics, node.NewKeyword);
 
            //  prepare
            var initializers = node.Initializers;
            int fieldCount = initializers.Count;
            bool hasError = false;
 
            //  bind field initializers
            BoundExpression[] boundExpressions = new BoundExpression[fieldCount];
            AnonymousTypeField[] fields = new AnonymousTypeField[fieldCount];
            CSharpSyntaxNode[] fieldSyntaxNodes = new CSharpSyntaxNode[fieldCount];
 
            // WARNING: Note that SemanticModel.GetDeclaredSymbol for field initializer node relies on 
            //          the fact that the order of properties in anonymous type template corresponds 
            //          1-to-1 to the appropriate filed initializer syntax nodes; This means such 
            //          correspondence must be preserved all the time including erroneous scenarios
 
            // set of names already used
            var uniqueFieldNames = PooledHashSet<string>.GetInstance();
 
            for (int i = 0; i < fieldCount; i++)
            {
                AnonymousObjectMemberDeclaratorSyntax fieldInitializer = initializers[i];
                NameEqualsSyntax? nameEquals = fieldInitializer.NameEquals;
                ExpressionSyntax expression = fieldInitializer.Expression;
 
                SyntaxToken nameToken = default(SyntaxToken);
                if (nameEquals != null)
                {
                    nameToken = nameEquals.Name.Identifier;
                }
                else
                {
                    if (!IsAnonymousTypeMemberExpression(expression))
                    {
                        hasError = true;
                        diagnostics.Add(ErrorCode.ERR_InvalidAnonymousTypeMemberDeclarator, expression.GetLocation());
                    }
 
                    nameToken = expression.ExtractAnonymousTypeMemberName();
                }
 
                hasError |= expression.HasErrors;
                boundExpressions[i] = BindRValueWithoutTargetType(expression, diagnostics);
 
                //  check the name to be unique
                string? fieldName = null;
                if (nameToken.Kind() == SyntaxKind.IdentifierToken)
                {
                    fieldName = nameToken.ValueText;
                    if (!uniqueFieldNames.Add(fieldName!))
                    {
                        //  name duplication
                        Error(diagnostics, ErrorCode.ERR_AnonymousTypeDuplicatePropertyName, fieldInitializer);
                        hasError = true;
                        fieldName = null;
                    }
                }
                else
                {
                    // there is something wrong with field's name
                    hasError = true;
                }
 
                //  calculate the expression's type and report errors if needed
                TypeSymbol fieldType = GetAnonymousTypeFieldType(boundExpressions[i], fieldInitializer, diagnostics, ref hasError);
 
                // build anonymous type field descriptor
                fieldSyntaxNodes[i] = (nameToken.Kind() == SyntaxKind.IdentifierToken) ? (CSharpSyntaxNode)nameToken.Parent! : fieldInitializer;
                fields[i] = new AnonymousTypeField(
                    fieldName == null ? "$" + i.ToString() : fieldName,
                    fieldSyntaxNodes[i].Location,
                    TypeWithAnnotations.Create(fieldType),
                    RefKind.None,
                    ScopedKind.None);
 
                //  NOTE: ERR_InvalidAnonymousTypeMemberDeclarator (CS0746) would be generated by parser if needed
            }
 
            uniqueFieldNames.Free();
 
            //  Create anonymous type 
            AnonymousTypeManager manager = this.Compilation.AnonymousTypeManager;
            AnonymousTypeDescriptor descriptor = new AnonymousTypeDescriptor(fields.AsImmutableOrNull(), node.NewKeyword.GetLocation());
            NamedTypeSymbol anonymousType = manager.ConstructAnonymousTypeSymbol(descriptor);
 
            // declarators - bound nodes created for providing semantic info 
            // on anonymous type fields having explicitly specified name
            ArrayBuilder<BoundAnonymousPropertyDeclaration> declarators =
                ArrayBuilder<BoundAnonymousPropertyDeclaration>.GetInstance();
            for (int i = 0; i < fieldCount; i++)
            {
                NameEqualsSyntax? explicitName = initializers[i].NameEquals;
                if (explicitName != null)
                {
                    AnonymousTypeField field = fields[i];
                    if (field.Name != null)
                    {
                        //  get property symbol and create a bound property declaration node
                        foreach (var symbol in anonymousType.GetMembers(field.Name))
                        {
                            if (symbol.Kind == SymbolKind.Property)
                            {
                                declarators.Add(new BoundAnonymousPropertyDeclaration(fieldSyntaxNodes[i], (PropertySymbol)symbol, field.Type));
                                break;
                            }
                        }
                    }
                }
            }
 
            // check if anonymous object creation is allowed in this context
            if (!this.IsAnonymousTypesAllowed())
            {
                Error(diagnostics, ErrorCode.ERR_AnonymousTypeNotAvailable, node.NewKeyword);
                hasError = true;
            }
 
            //  Finally create a bound node
            return new BoundAnonymousObjectCreationExpression(
                node,
                anonymousType.InstanceConstructors[0],
                boundExpressions.AsImmutableOrNull(),
                declarators.ToImmutableAndFree(),
                anonymousType,
                hasError);
        }
 
        private static bool IsAnonymousTypeMemberExpression(ExpressionSyntax expr)
        {
            while (true)
            {
                switch (expr.Kind())
                {
                    case SyntaxKind.QualifiedName:
                        expr = ((QualifiedNameSyntax)expr).Right;
                        continue;
                    case SyntaxKind.ConditionalAccessExpression:
                        expr = ((ConditionalAccessExpressionSyntax)expr).WhenNotNull;
                        if (expr.Kind() == SyntaxKind.MemberBindingExpression)
                        {
                            return true;
                        }
 
                        continue;
                    case SyntaxKind.IdentifierName:
                    case SyntaxKind.SimpleMemberAccessExpression:
                        return true;
                    default:
                        return false;
                }
            }
        }
 
        /// <summary>
        /// Actually, defines if an error ERR_AnonymousTypeNotAvailable is to be generated; 
        /// 
        /// Dev10 rules (which are based on BindingContext::InMethod()) are difficult to 
        /// reproduce, so this implementation checks both current symbol as well as syntax nodes.
        /// </summary>
        private bool IsAnonymousTypesAllowed()
        {
            var member = this.ContainingMemberOrLambda;
            if (member is null)
            {
                return false;
            }
 
            switch (member.Kind)
            {
                case SymbolKind.Method:
                    return true;
 
                case SymbolKind.Field:
                    return !((FieldSymbol)member).IsConst;
 
                case SymbolKind.NamedType:
                    //  allow usage of anonymous types in script classes
                    return ((NamedTypeSymbol)member).IsScriptClass;
            }
 
            return false;
        }
 
        /// <summary>
        /// Returns the type to be used as a field type; generates errors in case the type is not
        /// supported for anonymous type fields.
        /// </summary>
        private TypeSymbol GetAnonymousTypeFieldType(BoundExpression expression, CSharpSyntaxNode errorSyntax, BindingDiagnosticBag diagnostics, ref bool hasError)
        {
            object? errorArg = null;
            TypeSymbol? expressionType = expression.Type;
 
            if (!expression.HasAnyErrors)
            {
                if (expression.HasExpressionType())
                {
                    RoslynDebug.Assert(expressionType is object);
                    if (expressionType.IsVoidType())
                    {
                        errorArg = expressionType;
                        expressionType = CreateErrorType(SyntaxFacts.GetText(SyntaxKind.VoidKeyword));
                    }
                    else if (expressionType.IsPointerOrFunctionPointer())
                    {
                        errorArg = expressionType;
                        // CONSIDER: we could use an explicit error type instead of the unsafe type.
                    }
                    else if (expressionType.IsRestrictedType())
                    {
                        errorArg = expressionType;
                    }
                }
                else
                {
                    errorArg = expression.Display;
                }
            }
 
            if (expressionType is null)
            {
                expressionType = CreateErrorType("error");
            }
 
            if (errorArg != null)
            {
                hasError = true;
                Error(diagnostics, ErrorCode.ERR_AnonymousTypePropertyAssignedBadValue, errorSyntax, errorArg);
                // NOTE: ERR_QueryRangeVariableAssignedBadValue is being generated 
                //       by query binding code and never reach this point
            }
 
            return expressionType;
        }
    }
}