| File: Symbols\Synthesized\Records\SynthesizedRecordClone.cs | Web Access |
| Project: src\roslyn\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; } } }