File: Symbols\Synthesized\Records\SynthesizedRecordClone.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.Immutable;
using System.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// If a virtual "clone" method is present in the base record, the synthesized "clone" method overrides it
    /// and the return type of the method is the current containing type if the "covariant returns" feature is
    /// supported and the override return type otherwise. An error is produced if the base record clone method
    /// is sealed. If a virtual "clone" method is not present in the base record, the return type of the clone
    /// method is the containing type and the method is virtual, unless the record is sealed or abstract.
    /// If the containing record is abstract, the synthesized clone method is also abstract.
    /// If the "clone" method is not abstract, it returns the result of a call to a copy constructor.
    /// </summary>
    internal sealed class SynthesizedRecordClone : SynthesizedRecordOrdinaryMethod
    {
        public SynthesizedRecordClone(
            SourceMemberContainerTypeSymbol containingType,
            int memberOffset)
            : base(containingType, WellKnownMemberNames.CloneMethodName, memberOffset, MakeDeclarationModifiers(containingType))
        {
            Debug.Assert(!containingType.IsRecordStruct);
        }
 
        private static DeclarationModifiers MakeDeclarationModifiers(SourceMemberContainerTypeSymbol containingType)
        {
            DeclarationModifiers result = DeclarationModifiers.Public;
 
            if (VirtualCloneInBase(containingType) is object)
            {
                result |= DeclarationModifiers.Override;
            }
            else
            {
                result |= containingType.IsSealed ? DeclarationModifiers.None : DeclarationModifiers.Virtual;
            }
 
            if (containingType.IsAbstract)
            {
                result &= ~DeclarationModifiers.Virtual;
                result |= DeclarationModifiers.Abstract;
            }
 
#if DEBUG
            Debug.Assert(modifiersAreValid(result));
#endif 
            return result;
 
#if DEBUG
            static bool modifiersAreValid(DeclarationModifiers modifiers)
            {
                if ((modifiers & DeclarationModifiers.AccessibilityMask) != DeclarationModifiers.Public)
                {
                    return false;
                }
 
                modifiers &= ~DeclarationModifiers.AccessibilityMask;
 
                switch (modifiers)
                {
                    case DeclarationModifiers.None:
                        return true;
                    case DeclarationModifiers.Abstract:
                        return true;
                    case DeclarationModifiers.Override:
                        return true;
                    case DeclarationModifiers.Abstract | DeclarationModifiers.Override:
                        return true;
                    case DeclarationModifiers.Virtual:
                        return true;
                    default:
                        return false;
                }
            }
#endif 
        }
 
        private static MethodSymbol? VirtualCloneInBase(NamedTypeSymbol containingType)
        {
            NamedTypeSymbol baseType = containingType.BaseTypeNoUseSiteDiagnostics;
 
            if (!baseType.IsObjectType())
            {
                var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; // This is reported when we bind bases
                return FindValidCloneMethod(baseType, ref discardedUseSiteInfo);
            }
 
            return null;
        }
 
        protected override (TypeWithAnnotations ReturnType, ImmutableArray<ParameterSymbol> Parameters) MakeParametersAndBindReturnType(BindingDiagnosticBag diagnostics)
        {
            return (ReturnType: !ContainingAssembly.RuntimeSupportsCovariantReturnsOfClasses && VirtualCloneInBase(ContainingType) is { } baseClone ?
                                     baseClone.ReturnTypeWithAnnotations :
                                     TypeWithAnnotations.Create(isNullableEnabled: true, ContainingType),
                    Parameters: ImmutableArray<ParameterSymbol>.Empty);
        }
 
        protected override int GetParameterCountFromSyntax() => 0;
 
        internal override void GenerateMethodBody(TypeCompilationState compilationState, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(!IsAbstract);
 
            var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics);
 
            try
            {
                if (ReturnType.IsErrorType())
                {
                    F.CloseMethod(F.ThrowNull());
                    return;
                }
 
                var members = ContainingType.InstanceConstructors;
                foreach (var member in members)
                {
                    var ctor = (MethodSymbol)member;
                    if (ctor.ParameterCount == 1 && ctor.Parameters[0].RefKind == RefKind.None &&
                        ctor.Parameters[0].Type.Equals(ContainingType, TypeCompareKind.AllIgnoreOptions))
                    {
                        F.CloseMethod(F.Return(F.New(ctor, F.This())));
                        return;
                    }
                }
 
                throw ExceptionUtilities.Unreachable();
            }
            catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex)
            {
                diagnostics.Add(ex.Diagnostic);
                F.CloseMethod(F.ThrowNull());
            }
        }
 
        internal static MethodSymbol? FindValidCloneMethod(TypeSymbol containingType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            if (containingType.IsObjectType() || containingType is not NamedTypeSymbol containingNamedType)
            {
                return null;
            }
 
            // If this symbol is from metadata, getting all members can cause us to realize a lot of structures that we otherwise
            // don't have to. Optimize for the common case here of there not being a method named <Clone>$. If there is a method
            // with that name, it's most likely the one we're interested in, and we can't get around loading everything to find it.
            if (!containingNamedType.HasPossibleWellKnownCloneMethod())
            {
                return null;
            }
 
            MethodSymbol? candidate = null;
 
            foreach (var member in containingType.GetMembers(WellKnownMemberNames.CloneMethodName))
            {
                if (member is MethodSymbol
                    {
                        DeclaredAccessibility: Accessibility.Public,
                        IsStatic: false,
                        ParameterCount: 0,
                        Arity: 0
                    } method)
                {
                    if (candidate is object)
                    {
                        // An ambiguity case, can come from metadata, treat as an error for simplicity.
                        return null;
                    }
 
                    candidate = method;
                }
            }
 
            if (candidate is null ||
                !(containingType.IsSealed || candidate.IsOverride || candidate.IsVirtual || candidate.IsAbstract) ||
                !containingType.IsEqualToOrDerivedFrom(
                    candidate.ReturnType,
                    TypeCompareKind.AllIgnoreOptions,
                    ref useSiteInfo))
            {
                return null;
            }
 
            return candidate;
        }
    }
}