// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
///
/// Represents a named type symbol whose members are declared in source.
///
internal abstract partial class SourceMemberContainerTypeSymbol : NamedTypeSymbol
{
// The flags type is used to compact many different bits of information efficiently.
private struct Flags
{
// We current pack everything into one 32-bit int; layout is given below.
//
// | |vvv|zzzz|f|d|yy|wwwwww|
//
// w = special type. 6 bits.
// y = IsManagedType. 2 bits.
// d = FieldDefinitionsNoted. 1 bit
// f = FlattenedMembersIsSorted. 1 bit.
// z = TypeKind. 4 bits.
// v = NullableContext. 3 bits.
private int _flags;
private const int SpecialTypeOffset = 0;
private const int SpecialTypeSize = 6;
private const int ManagedKindOffset = SpecialTypeOffset + SpecialTypeSize;
private const int ManagedKindSize = 2;
private const int FieldDefinitionsNotedOffset = ManagedKindOffset + ManagedKindSize;
private const int FieldDefinitionsNotedSize = 1;
private const int FlattenedMembersIsSortedOffset = FieldDefinitionsNotedOffset + FieldDefinitionsNotedSize;
private const int FlattenedMembersIsSortedSize = 1;
private const int TypeKindOffset = FlattenedMembersIsSortedOffset + FlattenedMembersIsSortedSize;
private const int TypeKindSize = 4;
private const int NullableContextOffset = TypeKindOffset + TypeKindSize;
private const int NullableContextSize = 3;
private const int SpecialTypeMask = (1 << SpecialTypeSize) - 1;
private const int ManagedKindMask = (1 << ManagedKindSize) - 1;
private const int TypeKindMask = (1 << TypeKindSize) - 1;
private const int NullableContextMask = (1 << NullableContextSize) - 1;
private const int FieldDefinitionsNotedBit = 1 << FieldDefinitionsNotedOffset;
private const int FlattenedMembersIsSortedBit = 1 << FlattenedMembersIsSortedOffset;
public SpecialType SpecialType
{
get { return (SpecialType)((_flags >> SpecialTypeOffset) & SpecialTypeMask); }
}
public ManagedKind ManagedKind
{
get { return (ManagedKind)((_flags >> ManagedKindOffset) & ManagedKindMask); }
}
public bool FieldDefinitionsNoted
{
get { return (_flags & FieldDefinitionsNotedBit) != 0; }
}
// True if "lazyMembersFlattened" is sorted.
public bool FlattenedMembersIsSorted
{
get { return (_flags & FlattenedMembersIsSortedBit) != 0; }
}
public TypeKind TypeKind
{
get { return (TypeKind)((_flags >> TypeKindOffset) & TypeKindMask); }
}
#if DEBUG
static Flags()
{
// Verify masks are sufficient for values.
Debug.Assert(EnumUtilities.ContainsAllValues(SpecialTypeMask));
Debug.Assert(EnumUtilities.ContainsAllValues(NullableContextMask));
}
#endif
public Flags(SpecialType specialType, TypeKind typeKind)
{
int specialTypeInt = ((int)specialType & SpecialTypeMask) << SpecialTypeOffset;
int typeKindInt = ((int)typeKind & TypeKindMask) << TypeKindOffset;
_flags = specialTypeInt | typeKindInt;
}
public void SetFieldDefinitionsNoted()
{
ThreadSafeFlagOperations.Set(ref _flags, FieldDefinitionsNotedBit);
}
public void SetFlattenedMembersIsSorted()
{
ThreadSafeFlagOperations.Set(ref _flags, (FlattenedMembersIsSortedBit));
}
private static bool BitsAreUnsetOrSame(int bits, int mask)
{
return (bits & mask) == 0 || (bits & mask) == mask;
}
public void SetManagedKind(ManagedKind managedKind)
{
int bitsToSet = ((int)managedKind & ManagedKindMask) << ManagedKindOffset;
Debug.Assert(BitsAreUnsetOrSame(_flags, bitsToSet));
ThreadSafeFlagOperations.Set(ref _flags, bitsToSet);
}
public bool TryGetNullableContext(out byte? value)
{
return ((NullableContextKind)((_flags >> NullableContextOffset) & NullableContextMask)).TryGetByte(out value);
}
public bool SetNullableContext(byte? value)
{
return ThreadSafeFlagOperations.Set(ref _flags, (((int)value.ToNullableContextFlags() & NullableContextMask) << NullableContextOffset));
}
}
protected SymbolCompletionState state;
private Flags _flags;
private readonly DeclarationModifiers _declModifiers;
private readonly NamespaceOrTypeSymbol _containingSymbol;
protected readonly MergedTypeDeclaration declaration;
private MembersAndInitializers? _lazyMembersAndInitializers;
private Dictionary>? _lazyMembersDictionary;
private Dictionary>? _lazyEarlyAttributeDecodingMembersDictionary;
private static readonly Dictionary> s_emptyTypeMembers = new Dictionary>(EmptyComparer.Instance);
private Dictionary>? _lazyTypeMembers;
private ImmutableArray _lazyMembersFlattened;
private ImmutableArray _lazySynthesizedExplicitImplementations;
private int _lazyKnownCircularStruct;
private LexicalSortKey _lazyLexicalSortKey = LexicalSortKey.NotInitialized;
private ThreeState _lazyContainsExtensionMethods;
private ThreeState _lazyAnyMemberHasAttributes;
#region Construction
internal SourceMemberContainerTypeSymbol(
NamespaceOrTypeSymbol containingSymbol,
MergedTypeDeclaration declaration,
DiagnosticBag diagnostics,
TupleExtraData? tupleData = null)
: base(tupleData)
{
_containingSymbol = containingSymbol;
this.declaration = declaration;
TypeKind typeKind = declaration.Kind.ToTypeKind();
var modifiers = MakeModifiers(typeKind, diagnostics);
foreach (var singleDeclaration in declaration.Declarations)
{
diagnostics.AddRange(singleDeclaration.Diagnostics);
}
int access = (int)(modifiers & DeclarationModifiers.AccessibilityMask);
if ((access & (access - 1)) != 0)
{ // more than one access modifier
if ((modifiers & DeclarationModifiers.Partial) != 0)
diagnostics.Add(ErrorCode.ERR_PartialModifierConflict, Locations[0], this);
access = access & ~(access - 1); // narrow down to one access modifier
modifiers &= ~DeclarationModifiers.AccessibilityMask; // remove them all
modifiers |= (DeclarationModifiers)access; // except the one
}
_declModifiers = modifiers;
var specialType = access == (int)DeclarationModifiers.Public
? MakeSpecialType()
: SpecialType.None;
_flags = new Flags(specialType, typeKind);
var containingType = this.ContainingType;
if (containingType?.IsSealed == true && this.DeclaredAccessibility.HasProtected())
{
diagnostics.Add(AccessCheck.GetProtectedMemberInSealedTypeError(ContainingType), Locations[0], this);
}
state.NotePartComplete(CompletionPart.TypeArguments); // type arguments need not be computed separately
}
private SpecialType MakeSpecialType()
{
// check if this is one of the COR library types
if (ContainingSymbol.Kind == SymbolKind.Namespace &&
ContainingSymbol.ContainingAssembly.KeepLookingForDeclaredSpecialTypes)
{
//for a namespace, the emitted name is a dot-separated list of containing namespaces
var emittedName = ContainingSymbol.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat);
emittedName = MetadataHelpers.BuildQualifiedName(emittedName, MetadataName);
return SpecialTypes.GetTypeFromMetadataName(emittedName);
}
else
{
return SpecialType.None;
}
}
private DeclarationModifiers MakeModifiers(TypeKind typeKind, DiagnosticBag diagnostics)
{
Symbol containingSymbol = this.ContainingSymbol;
DeclarationModifiers defaultAccess;
var allowedModifiers = DeclarationModifiers.AccessibilityMask;
if (containingSymbol.Kind == SymbolKind.Namespace)
{
defaultAccess = DeclarationModifiers.Internal;
}
else
{
allowedModifiers |= DeclarationModifiers.New;
if (((NamedTypeSymbol)containingSymbol).IsInterface)
{
defaultAccess = DeclarationModifiers.Public;
}
else
{
defaultAccess = DeclarationModifiers.Private;
}
}
switch (typeKind)
{
case TypeKind.Class:
case TypeKind.Submission:
allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.Static | DeclarationModifiers.Sealed | DeclarationModifiers.Abstract
| DeclarationModifiers.Unsafe | DeclarationModifiers.Data;
break;
case TypeKind.Struct:
allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.Ref | DeclarationModifiers.ReadOnly | DeclarationModifiers.Unsafe | DeclarationModifiers.Data;
break;
case TypeKind.Interface:
allowedModifiers |= DeclarationModifiers.Partial | DeclarationModifiers.Unsafe;
break;
case TypeKind.Delegate:
allowedModifiers |= DeclarationModifiers.Unsafe;
break;
}
bool modifierErrors;
var mods = MakeAndCheckTypeModifiers(
defaultAccess,
allowedModifiers,
this,
diagnostics,
out modifierErrors);
this.CheckUnsafeModifier(mods, diagnostics);
if (!modifierErrors &&
(mods & DeclarationModifiers.Abstract) != 0 &&
(mods & (DeclarationModifiers.Sealed | DeclarationModifiers.Static)) != 0)
{
diagnostics.Add(ErrorCode.ERR_AbstractSealedStatic, Locations[0], this);
}
if (!modifierErrors &&
(mods & (DeclarationModifiers.Sealed | DeclarationModifiers.Static)) == (DeclarationModifiers.Sealed | DeclarationModifiers.Static))
{
diagnostics.Add(ErrorCode.ERR_SealedStaticClass, Locations[0], this);
}
switch (typeKind)
{
case TypeKind.Interface:
mods |= DeclarationModifiers.Abstract;
break;
case TypeKind.Struct:
case TypeKind.Enum:
mods |= DeclarationModifiers.Sealed;
break;
case TypeKind.Delegate:
mods |= DeclarationModifiers.Sealed;
break;
}
return mods;
}
private DeclarationModifiers MakeAndCheckTypeModifiers(
DeclarationModifiers defaultAccess,
DeclarationModifiers allowedModifiers,
SourceMemberContainerTypeSymbol self,
DiagnosticBag diagnostics,
out bool modifierErrors)
{
modifierErrors = false;
var result = DeclarationModifiers.Unset;
var partCount = declaration.Declarations.Length;
var missingPartial = false;
for (var i = 0; i < partCount; i++)
{
var decl = declaration.Declarations[i];
var mods = decl.Modifiers;
if (partCount > 1 && (mods & DeclarationModifiers.Partial) == 0)
{
missingPartial = true;
}
if (!modifierErrors)
{
mods = ModifierUtils.CheckModifiers(
mods, allowedModifiers, declaration.Declarations[i].NameLocation, diagnostics,
modifierTokens: null, modifierErrors: out modifierErrors);
// It is an error for the same modifier to appear multiple times.
if (!modifierErrors)
{
var info = ModifierUtils.CheckAccessibility(mods, this, isExplicitInterfaceImplementation: false);
if (info != null)
{
diagnostics.Add(info, self.Locations[0]);
modifierErrors = true;
}
}
}
if (result == DeclarationModifiers.Unset)
{
result = mods;
}
else
{
result |= mods;
}
}
if ((result & DeclarationModifiers.AccessibilityMask) == 0)
{
result |= defaultAccess;
}
if (missingPartial)
{
if ((result & DeclarationModifiers.Partial) == 0)
{
// duplicate definitions
switch (self.ContainingSymbol.Kind)
{
case SymbolKind.Namespace:
for (var i = 1; i < partCount; i++)
{
diagnostics.Add(ErrorCode.ERR_DuplicateNameInNS, declaration.Declarations[i].NameLocation, self.Name, self.ContainingSymbol);
modifierErrors = true;
}
break;
case SymbolKind.NamedType:
for (var i = 1; i < partCount; i++)
{
if (ContainingType!.Locations.Length == 1 || ContainingType.IsPartial())
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, declaration.Declarations[i].NameLocation, self.ContainingSymbol, self.Name);
modifierErrors = true;
}
break;
}
}
else
{
for (var i = 0; i < partCount; i++)
{
var singleDeclaration = declaration.Declarations[i];
var mods = singleDeclaration.Modifiers;
if ((mods & DeclarationModifiers.Partial) == 0)
{
diagnostics.Add(ErrorCode.ERR_MissingPartial, singleDeclaration.NameLocation, self.Name);
modifierErrors = true;
}
}
}
}
return result;
}
#endregion
#region Completion
internal sealed override bool RequiresCompletion
{
get { return true; }
}
internal sealed override bool HasComplete(CompletionPart part)
{
return state.HasComplete(part);
}
protected abstract void CheckBase(DiagnosticBag diagnostics);
protected abstract void CheckInterfaces(DiagnosticBag diagnostics);
internal override void ForceComplete(SourceLocation? locationOpt, CancellationToken cancellationToken)
{
while (true)
{
// NOTE: cases that depend on GetMembers[ByName] should call RequireCompletionPartMembers.
cancellationToken.ThrowIfCancellationRequested();
var incompletePart = state.NextIncompletePart;
switch (incompletePart)
{
case CompletionPart.Attributes:
GetAttributes();
break;
case CompletionPart.StartBaseType:
case CompletionPart.FinishBaseType:
if (state.NotePartComplete(CompletionPart.StartBaseType))
{
var diagnostics = DiagnosticBag.GetInstance();
CheckBase(diagnostics);
AddDeclarationDiagnostics(diagnostics);
state.NotePartComplete(CompletionPart.FinishBaseType);
diagnostics.Free();
}
break;
case CompletionPart.StartInterfaces:
case CompletionPart.FinishInterfaces:
if (state.NotePartComplete(CompletionPart.StartInterfaces))
{
var diagnostics = DiagnosticBag.GetInstance();
CheckInterfaces(diagnostics);
AddDeclarationDiagnostics(diagnostics);
state.NotePartComplete(CompletionPart.FinishInterfaces);
diagnostics.Free();
}
break;
case CompletionPart.EnumUnderlyingType:
var discarded = this.EnumUnderlyingType;
break;
case CompletionPart.TypeArguments:
{
var tmp = this.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics; // force type arguments
}
break;
case CompletionPart.TypeParameters:
// force type parameters
foreach (var typeParameter in this.TypeParameters)
{
typeParameter.ForceComplete(locationOpt, cancellationToken);
}
state.NotePartComplete(CompletionPart.TypeParameters);
break;
case CompletionPart.Members:
this.GetMembersByName();
break;
case CompletionPart.TypeMembers:
this.GetTypeMembersUnordered();
break;
case CompletionPart.SynthesizedExplicitImplementations:
this.GetSynthesizedExplicitImplementations(cancellationToken); //force interface and base class errors to be checked
break;
case CompletionPart.StartMemberChecks:
case CompletionPart.FinishMemberChecks:
if (state.NotePartComplete(CompletionPart.StartMemberChecks))
{
var diagnostics = DiagnosticBag.GetInstance();
AfterMembersChecks(diagnostics);
AddDeclarationDiagnostics(diagnostics);
// We may produce a SymbolDeclaredEvent for the enclosing type before events for its contained members
DeclaringCompilation.SymbolDeclaredEvent(this);
var thisThreadCompleted = state.NotePartComplete(CompletionPart.FinishMemberChecks);
Debug.Assert(thisThreadCompleted);
diagnostics.Free();
}
break;
case CompletionPart.MembersCompleted:
{
ImmutableArray members = this.GetMembersUnordered();
bool allCompleted = true;
if (locationOpt == null)
{
foreach (var member in members)
{
cancellationToken.ThrowIfCancellationRequested();
member.ForceComplete(locationOpt, cancellationToken);
}
}
else
{
foreach (var member in members)
{
ForceCompleteMemberByLocation(locationOpt, member, cancellationToken);
allCompleted = allCompleted && member.HasComplete(CompletionPart.All);
}
}
if (!allCompleted)
{
// We did not complete all members so we won't have enough information for
// the PointedAtManagedTypeChecks, so just kick out now.
var allParts = CompletionPart.NamedTypeSymbolWithLocationAll;
state.SpinWaitComplete(allParts, cancellationToken);
return;
}
EnsureFieldDefinitionsNoted();
// We've completed all members, so we're ready for the PointedAtManagedTypeChecks;
// proceed to the next iteration.
state.NotePartComplete(CompletionPart.MembersCompleted);
break;
}
case CompletionPart.None:
return;
default:
// This assert will trigger if we forgot to handle any of the completion parts
Debug.Assert((incompletePart & CompletionPart.NamedTypeSymbolAll) == 0);
// any other values are completion parts intended for other kinds of symbols
state.NotePartComplete(CompletionPart.All & ~CompletionPart.NamedTypeSymbolAll);
break;
}
state.SpinWaitComplete(incompletePart, cancellationToken);
}
throw ExceptionUtilities.Unreachable;
}
internal void EnsureFieldDefinitionsNoted()
{
if (_flags.FieldDefinitionsNoted)
{
return;
}
NoteFieldDefinitions();
}
private void NoteFieldDefinitions()
{
// we must note all fields once therefore we need to lock
var membersAndInitializers = this.GetMembersAndInitializers();
lock (membersAndInitializers)
{
if (!_flags.FieldDefinitionsNoted)
{
var assembly = (SourceAssemblySymbol)ContainingAssembly;
Accessibility containerEffectiveAccessibility = EffectiveAccessibility();
foreach (var member in membersAndInitializers.NonTypeNonIndexerMembers)
{
FieldSymbol field;
if (!member.IsFieldOrFieldLikeEvent(out field) || field.IsConst || field.IsFixedSizeBuffer)
{
continue;
}
Accessibility fieldDeclaredAccessibility = field.DeclaredAccessibility;
if (fieldDeclaredAccessibility == Accessibility.Private)
{
// mark private fields as tentatively unassigned and unread unless we discover otherwise.
assembly.NoteFieldDefinition(field, isInternal: false, isUnread: true);
}
else if (containerEffectiveAccessibility == Accessibility.Private)
{
// mark effectively private fields as tentatively unassigned unless we discover otherwise.
assembly.NoteFieldDefinition(field, isInternal: false, isUnread: false);
}
else if (fieldDeclaredAccessibility == Accessibility.Internal || containerEffectiveAccessibility == Accessibility.Internal)
{
// mark effectively internal fields as tentatively unassigned unless we discover otherwise.
// NOTE: These fields will be reported as unassigned only if internals are not visible from this assembly.
// See property SourceAssemblySymbol.UnusedFieldWarnings.
assembly.NoteFieldDefinition(field, isInternal: true, isUnread: false);
}
}
_flags.SetFieldDefinitionsNoted();
}
}
}
#endregion
#region Containers
public sealed override NamedTypeSymbol? ContainingType
{
get
{
return _containingSymbol as NamedTypeSymbol;
}
}
public sealed override Symbol ContainingSymbol
{
get
{
return _containingSymbol;
}
}
#endregion
#region Flags Encoded Properties
public override SpecialType SpecialType
{
get
{
return _flags.SpecialType;
}
}
public override TypeKind TypeKind
{
get
{
return _flags.TypeKind;
}
}
internal MergedTypeDeclaration MergedDeclaration
{
get
{
return this.declaration;
}
}
internal sealed override bool IsInterface
{
get
{
// TypeKind is computed eagerly, so this is cheap.
return this.TypeKind == TypeKind.Interface;
}
}
internal override ManagedKind ManagedKind
{
get
{
var managedKind = _flags.ManagedKind;
if (managedKind == ManagedKind.Unknown)
{
var baseKind = base.ManagedKind;
_flags.SetManagedKind(baseKind);
return baseKind;
}
return managedKind;
}
}
public override bool IsStatic => _declModifiers.HasFlag(DeclarationModifiers.Static);
public sealed override bool IsRefLikeType => _declModifiers.HasFlag(DeclarationModifiers.Ref);
public override bool IsReadOnly => _declModifiers.HasFlag(DeclarationModifiers.ReadOnly);
public override bool IsSealed => _declModifiers.HasFlag(DeclarationModifiers.Sealed);
public override bool IsAbstract => _declModifiers.HasFlag(DeclarationModifiers.Abstract);
internal bool IsPartial => _declModifiers.HasFlag(DeclarationModifiers.Partial);
internal bool IsNew => _declModifiers.HasFlag(DeclarationModifiers.New);
public override Accessibility DeclaredAccessibility
{
get
{
return ModifierUtils.EffectiveAccessibility(_declModifiers);
}
}
///
/// Compute the "effective accessibility" of the current class for the purpose of warnings about unused fields.
///
private Accessibility EffectiveAccessibility()
{
var result = DeclaredAccessibility;
if (result == Accessibility.Private) return Accessibility.Private;
for (Symbol? container = this.ContainingType; !(container is null); container = container.ContainingType)
{
switch (container.DeclaredAccessibility)
{
case Accessibility.Private:
return Accessibility.Private;
case Accessibility.Internal:
result = Accessibility.Internal;
continue;
}
}
return result;
}
#endregion
#region Syntax
public override bool IsScriptClass
{
get
{
var kind = this.declaration.Declarations[0].Kind;
return kind == DeclarationKind.Script || kind == DeclarationKind.Submission;
}
}
public override bool IsImplicitClass
{
get
{
return this.declaration.Declarations[0].Kind == DeclarationKind.ImplicitClass;
}
}
public override bool IsImplicitlyDeclared
{
get
{
return IsImplicitClass || IsScriptClass;
}
}
public override int Arity
{
get
{
return declaration.Arity;
}
}
public override string Name
{
get
{
return declaration.Name;
}
}
internal override bool MangleName
{
get
{
return Arity > 0;
}
}
internal override LexicalSortKey GetLexicalSortKey()
{
if (!_lazyLexicalSortKey.IsInitialized)
{
_lazyLexicalSortKey.SetFrom(declaration.GetLexicalSortKey(this.DeclaringCompilation));
}
return _lazyLexicalSortKey;
}
public override ImmutableArray Locations
{
get
{
return declaration.NameLocations.Cast();
}
}
public ImmutableArray SyntaxReferences
{
get
{
return this.declaration.SyntaxReferences;
}
}
public override ImmutableArray DeclaringSyntaxReferences
{
get
{
return SyntaxReferences;
}
}
// This method behaves the same was as the base class, but avoids allocations associated with DeclaringSyntaxReferences
internal override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken)
{
var declarations = declaration.Declarations;
if (IsImplicitlyDeclared && declarations.IsEmpty)
{
return ContainingSymbol.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken);
}
foreach (var declaration in declarations)
{
cancellationToken.ThrowIfCancellationRequested();
var syntaxRef = declaration.SyntaxReference;
if (syntaxRef.SyntaxTree == tree &&
(!definedWithinSpan.HasValue || syntaxRef.Span.IntersectsWith(definedWithinSpan.Value)))
{
return true;
}
}
return false;
}
#endregion
#region Members
///
/// Encapsulates information about the non-type members of a (i.e. this) type.
/// 1) For non-initializers, symbols are created and stored in a list.
/// 2) For fields and properties, the symbols are stored in (1) and their initializers are
/// stored with other initialized fields and properties from the same syntax tree with
/// the same static-ness.
/// 3) For indexers, syntax (weak) references are stored for later binding.
///
///
/// CONSIDER: most types won't have indexers, so we could move the indexer list
/// into a subclass to spare most instances the space required for the field.
///
private sealed class MembersAndInitializers
{
internal readonly ImmutableArray NonTypeNonIndexerMembers;
internal readonly ImmutableArray> StaticInitializers;
internal readonly ImmutableArray> InstanceInitializers;
internal readonly ImmutableArray IndexerDeclarations;
internal readonly int StaticInitializersSyntaxLength;
internal readonly int InstanceInitializersSyntaxLength;
public MembersAndInitializers(
ImmutableArray nonTypeNonIndexerMembers,
ImmutableArray> staticInitializers,
ImmutableArray> instanceInitializers,
ImmutableArray indexerDeclarations,
int staticInitializersSyntaxLength,
int instanceInitializersSyntaxLength)
{
Debug.Assert(!nonTypeNonIndexerMembers.IsDefault);
Debug.Assert(!staticInitializers.IsDefault);
Debug.Assert(!instanceInitializers.IsDefault);
Debug.Assert(!indexerDeclarations.IsDefault);
Debug.Assert(!nonTypeNonIndexerMembers.Any(s => s is TypeSymbol));
Debug.Assert(!nonTypeNonIndexerMembers.Any(s => s.IsIndexer()));
Debug.Assert(!nonTypeNonIndexerMembers.Any(s => s.IsAccessor() && ((MethodSymbol)s).AssociatedSymbol.IsIndexer()));
Debug.Assert(staticInitializersSyntaxLength == staticInitializers.Sum(s => s.Sum(i => (i.FieldOpt == null || !i.FieldOpt.IsMetadataConstant) ? i.Syntax.Span.Length : 0)));
Debug.Assert(instanceInitializersSyntaxLength == instanceInitializers.Sum(s => s.Sum(i => i.Syntax.Span.Length)));
this.NonTypeNonIndexerMembers = nonTypeNonIndexerMembers;
this.StaticInitializers = staticInitializers;
this.InstanceInitializers = instanceInitializers;
this.IndexerDeclarations = indexerDeclarations;
this.StaticInitializersSyntaxLength = staticInitializersSyntaxLength;
this.InstanceInitializersSyntaxLength = instanceInitializersSyntaxLength;
}
}
internal ImmutableArray> StaticInitializers
{
get { return GetMembersAndInitializers().StaticInitializers; }
}
internal ImmutableArray> InstanceInitializers
{
get { return GetMembersAndInitializers().InstanceInitializers; }
}
internal int CalculateSyntaxOffsetInSynthesizedConstructor(int position, SyntaxTree tree, bool isStatic)
{
if (IsScriptClass && !isStatic)
{
int aggregateLength = 0;
foreach (var declaration in this.declaration.Declarations)
{
var syntaxRef = declaration.SyntaxReference;
if (tree == syntaxRef.SyntaxTree)
{
return aggregateLength + position;
}
aggregateLength += syntaxRef.Span.Length;
}
throw ExceptionUtilities.Unreachable;
}
int syntaxOffset;
if (TryCalculateSyntaxOffsetOfPositionInInitializer(position, tree, isStatic, ctorInitializerLength: 0, syntaxOffset: out syntaxOffset))
{
return syntaxOffset;
}
if (declaration.Declarations.Length >= 1 && position == declaration.Declarations[0].Location.SourceSpan.Start)
{
// With dynamic analysis instrumentation, the introducing declaration of a type can provide
// the syntax associated with both the analysis payload local of a synthesized constructor
// and with the constructor itself. If the synthesized constructor includes an initializer with a lambda,
// that lambda needs a closure that captures the analysis payload of the constructor,
// and the offset of the syntax for the local within the constructor is by definition zero.
return 0;
}
// an implicit constructor has no body and no initializer, so the variable has to be declared in a member initializer
throw ExceptionUtilities.Unreachable;
}
///
/// Calculates a syntax offset of a syntax position that is contained in a property or field initializer (if it is in fact contained in one).
///
internal bool TryCalculateSyntaxOffsetOfPositionInInitializer(int position, SyntaxTree tree, bool isStatic, int ctorInitializerLength, out int syntaxOffset)
{
Debug.Assert(ctorInitializerLength >= 0);
var membersAndInitializers = GetMembersAndInitializers();
var allInitializers = isStatic ? membersAndInitializers.StaticInitializers : membersAndInitializers.InstanceInitializers;
var siblingInitializers = GetInitializersInSourceTree(tree, allInitializers);
int index = IndexOfInitializerContainingPosition(siblingInitializers, position);
if (index < 0)
{
syntaxOffset = 0;
return false;
}
// |<-----------distanceFromCtorBody----------->|
// [ initializer 0 ][ initializer 1 ][ initializer 2 ][ctor initializer][ctor body]
// |<--preceding init len-->| ^
// position
int initializersLength = isStatic ? membersAndInitializers.StaticInitializersSyntaxLength : membersAndInitializers.InstanceInitializersSyntaxLength;
int distanceFromInitializerStart = position - siblingInitializers[index].Syntax.Span.Start;
int distanceFromCtorBody =
initializersLength + ctorInitializerLength -
(siblingInitializers[index].PrecedingInitializersLength + distanceFromInitializerStart);
Debug.Assert(distanceFromCtorBody > 0);
// syntax offset 0 is at the start of the ctor body:
syntaxOffset = -distanceFromCtorBody;
return true;
}
private static ImmutableArray GetInitializersInSourceTree(SyntaxTree tree, ImmutableArray> initializers)
{
var builder = ArrayBuilder.GetInstance();
foreach (var siblingInitializers in initializers)
{
Debug.Assert(!siblingInitializers.IsEmpty);
if (siblingInitializers[0].Syntax.SyntaxTree == tree)
{
builder.AddRange(siblingInitializers);
}
}
return builder.ToImmutableAndFree();
}
private static int IndexOfInitializerContainingPosition(ImmutableArray initializers, int position)
{
// Search for the start of the span (the spans are non-overlapping and sorted)
int index = initializers.BinarySearch(position, (initializer, pos) => initializer.Syntax.Span.Start.CompareTo(pos));
// Binary search returns non-negative result if the position is exactly the start of some span.
if (index >= 0)
{
return index;
}
// Otherwise, ~index is the closest span whose start is greater than the position.
// => Check if the preceding initializer span contains the position.
int precedingInitializerIndex = ~index - 1;
if (precedingInitializerIndex >= 0 && initializers[precedingInitializerIndex].Syntax.Span.Contains(position))
{
return precedingInitializerIndex;
}
return -1;
}
public override IEnumerable MemberNames
{
get
{
return IsTupleType ? GetMembers().Select(m => m.Name).Distinct() : this.declaration.MemberNames;
}
}
internal override ImmutableArray GetTypeMembersUnordered()
{
return GetTypeMembersDictionary().Flatten();
}
public override ImmutableArray GetTypeMembers()
{
return GetTypeMembersDictionary().Flatten(LexicalOrderSymbolComparer.Instance);
}
public override ImmutableArray GetTypeMembers(string name)
{
ImmutableArray members;
if (GetTypeMembersDictionary().TryGetValue(name, out members))
{
return members;
}
return ImmutableArray.Empty;
}
public override ImmutableArray GetTypeMembers(string name, int arity)
{
return GetTypeMembers(name).WhereAsArray(t => t.Arity == arity);
}
private Dictionary> GetTypeMembersDictionary()
{
if (_lazyTypeMembers == null)
{
var diagnostics = DiagnosticBag.GetInstance();
if (Interlocked.CompareExchange(ref _lazyTypeMembers, MakeTypeMembers(diagnostics), null) == null)
{
AddDeclarationDiagnostics(diagnostics);
state.NotePartComplete(CompletionPart.TypeMembers);
}
diagnostics.Free();
}
return _lazyTypeMembers;
}
private Dictionary> MakeTypeMembers(DiagnosticBag diagnostics)
{
var symbols = ArrayBuilder.GetInstance();
var conflictDict = new Dictionary<(string, int), SourceNamedTypeSymbol>();
try
{
foreach (var childDeclaration in declaration.Children)
{
var t = new SourceNamedTypeSymbol(this, childDeclaration, diagnostics);
this.CheckMemberNameDistinctFromType(t, diagnostics);
var key = (t.Name, t.Arity);
SourceNamedTypeSymbol other;
if (conflictDict.TryGetValue(key, out other))
{
if (Locations.Length == 1 || IsPartial)
{
if (t.IsPartial && other.IsPartial)
{
diagnostics.Add(ErrorCode.ERR_PartialTypeKindConflict, t.Locations[0], t);
}
else
{
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, t.Locations[0], this, t.Name);
}
}
}
else
{
conflictDict.Add(key, t);
}
symbols.Add(t);
}
if (IsInterface)
{
foreach (var t in symbols)
{
Binder.CheckFeatureAvailability(t.DeclaringSyntaxReferences[0].GetSyntax(), MessageID.IDS_DefaultInterfaceImplementation, diagnostics, t.Locations[0]);
}
}
Debug.Assert(s_emptyTypeMembers.Count == 0);
return symbols.Count > 0 ?
symbols.ToDictionary(s => s.Name, StringOrdinalComparer.Instance) :
s_emptyTypeMembers;
}
finally
{
symbols.Free();
}
}
private void CheckMemberNameDistinctFromType(Symbol member, DiagnosticBag diagnostics)
{
switch (this.TypeKind)
{
case TypeKind.Class:
case TypeKind.Struct:
if (member.Name == this.Name)
{
diagnostics.Add(ErrorCode.ERR_MemberNameSameAsType, member.Locations[0], this.Name);
}
break;
case TypeKind.Interface:
if (member.IsStatic)
{
goto case TypeKind.Class;
}
break;
}
}
internal override ImmutableArray GetMembersUnordered()
{
var result = _lazyMembersFlattened;
if (result.IsDefault)
{
result = GetMembersByName().Flatten(null); // do not sort.
ImmutableInterlocked.InterlockedInitialize(ref _lazyMembersFlattened, result);
result = _lazyMembersFlattened;
}
return result.ConditionallyDeOrder();
}
public override ImmutableArray GetMembers()
{
if (_flags.FlattenedMembersIsSorted)
{
return _lazyMembersFlattened;
}
else
{
var allMembers = this.GetMembersUnordered();
if (allMembers.Length > 1)
{
// The array isn't sorted. Sort it and remember that we sorted it.
allMembers = allMembers.Sort(LexicalOrderSymbolComparer.Instance);
ImmutableInterlocked.InterlockedExchange(ref _lazyMembersFlattened, allMembers);
}
_flags.SetFlattenedMembersIsSorted();
return allMembers;
}
}
public sealed override ImmutableArray GetMembers(string name)
{
ImmutableArray members;
if (GetMembersByName().TryGetValue(name, out members))
{
return members;
}
return ImmutableArray.Empty;
}
internal override ImmutableArray GetSimpleNonTypeMembers(string name)
{
if (_lazyMembersDictionary != null || declaration.MemberNames.Contains(name))
{
return GetMembers(name);
}
return ImmutableArray.Empty;
}
internal override IEnumerable GetFieldsToEmit()
{
if (this.TypeKind == TypeKind.Enum)
{
// For consistency with Dev10, emit value__ field first.
var valueField = ((SourceNamedTypeSymbol)this).EnumValueField;
RoslynDebug.Assert((object)valueField != null);
yield return valueField;
}
foreach (var m in this.GetMembers())
{
switch (m.Kind)
{
case SymbolKind.Field:
yield return (FieldSymbol)m;
break;
case SymbolKind.Event:
FieldSymbol associatedField = ((EventSymbol)m).AssociatedField;
if ((object)associatedField != null)
{
yield return associatedField;
}
break;
}
}
}
///
/// During early attribute decoding, we consider a safe subset of all members that will not
/// cause cyclic dependencies. Get all such members for this symbol.
///
/// In particular, this method will return nested types and fields (other than auto-property
/// backing fields).
///
internal override ImmutableArray GetEarlyAttributeDecodingMembers()
{
return GetEarlyAttributeDecodingMembersDictionary().Flatten();
}
///
/// During early attribute decoding, we consider a safe subset of all members that will not
/// cause cyclic dependencies. Get all such members for this symbol that have a particular name.
///
/// In particular, this method will return nested types and fields (other than auto-property
/// backing fields).
///
internal override ImmutableArray GetEarlyAttributeDecodingMembers(string name)
{
ImmutableArray result;
return GetEarlyAttributeDecodingMembersDictionary().TryGetValue(name, out result) ? result : ImmutableArray.Empty;
}
private Dictionary> GetEarlyAttributeDecodingMembersDictionary()
{
if (_lazyEarlyAttributeDecodingMembersDictionary == null)
{
var membersAndInitializers = GetMembersAndInitializers(); //NOTE: separately cached
// NOTE: members were added in a single pass over the syntax, so they're already
// in lexical order.
var membersByName = membersAndInitializers.NonTypeNonIndexerMembers.ToDictionary(s => s.Name);
AddNestedTypesToDictionary(membersByName, GetTypeMembersDictionary());
Interlocked.CompareExchange(ref _lazyEarlyAttributeDecodingMembersDictionary, membersByName, null);
}
return _lazyEarlyAttributeDecodingMembersDictionary;
}
// NOTE: this method should do as little work as possible
// we often need to get members just to do a lookup.
// All additional checks and diagnostics may be not
// needed yet or at all.
private MembersAndInitializers GetMembersAndInitializers()
{
var membersAndInitializers = _lazyMembersAndInitializers;
if (membersAndInitializers != null)
{
return membersAndInitializers;
}
var diagnostics = DiagnosticBag.GetInstance();
membersAndInitializers = BuildMembersAndInitializers(diagnostics);
var alreadyKnown = Interlocked.CompareExchange(ref _lazyMembersAndInitializers, membersAndInitializers, null);
if (alreadyKnown != null)
{
diagnostics.Free();
return alreadyKnown;
}
AddDeclarationDiagnostics(diagnostics);
diagnostics.Free();
return membersAndInitializers!;
}
protected Dictionary> GetMembersByName()
{
if (this.state.HasComplete(CompletionPart.Members))
{
return _lazyMembersDictionary!;
}
return GetMembersByNameSlow();
}
private Dictionary> GetMembersByNameSlow()
{
if (_lazyMembersDictionary == null)
{
var diagnostics = DiagnosticBag.GetInstance();
var membersDictionary = MakeAllMembers(diagnostics);
if (Interlocked.CompareExchange(ref _lazyMembersDictionary, membersDictionary, null) == null)
{
var memberNames = ArrayBuilder.GetInstance(membersDictionary.Count);
memberNames.AddRange(membersDictionary.Keys);
MergePartialMembers(memberNames, membersDictionary, diagnostics);
memberNames.Free();
AddDeclarationDiagnostics(diagnostics);
state.NotePartComplete(CompletionPart.Members);
}
diagnostics.Free();
}
state.SpinWaitComplete(CompletionPart.Members, default(CancellationToken));
return _lazyMembersDictionary;
}
internal override IEnumerable GetInstanceFieldsAndEvents()
{
var membersAndInitializers = this.GetMembersAndInitializers();
return membersAndInitializers.NonTypeNonIndexerMembers.Where(IsInstanceFieldOrEvent);
}
protected void AfterMembersChecks(DiagnosticBag diagnostics)
{
if (IsInterface)
{
CheckInterfaceMembers(this.GetMembersAndInitializers().NonTypeNonIndexerMembers, diagnostics);
}
CheckMemberNamesDistinctFromType(diagnostics);
CheckMemberNameConflicts(diagnostics);
CheckSpecialMemberErrors(diagnostics);
CheckTypeParameterNameConflicts(diagnostics);
CheckAccessorNameConflicts(diagnostics);
bool unused = KnownCircularStruct;
CheckSequentialOnPartialType(diagnostics);
CheckForProtectedInStaticClass(diagnostics);
CheckForUnmatchedOperators(diagnostics);
var location = Locations[0];
var compilation = DeclaringCompilation;
if (this.IsRefLikeType)
{
compilation.EnsureIsByRefLikeAttributeExists(diagnostics, location, modifyCompilation: true);
}
if (this.IsReadOnly)
{
compilation.EnsureIsReadOnlyAttributeExists(diagnostics, location, modifyCompilation: true);
}
if (compilation.ShouldEmitNullableAttributes(this))
{
if (ShouldEmitNullableContextValue(out _))
{
compilation.EnsureNullableContextAttributeExists(diagnostics, location, modifyCompilation: true);
}
// https://github.com/dotnet/roslyn/issues/30080: Report diagnostics for base type and interfaces at more specific locations.
var baseType = BaseTypeNoUseSiteDiagnostics;
var interfaces = InterfacesNoUseSiteDiagnostics();
if (baseType?.NeedsNullableAttribute() == true ||
interfaces.Any(t => t.NeedsNullableAttribute()))
{
compilation.EnsureNullableAttributeExists(diagnostics, location, modifyCompilation: true);
}
}
}
private void CheckMemberNamesDistinctFromType(DiagnosticBag diagnostics)
{
foreach (var member in GetMembersAndInitializers().NonTypeNonIndexerMembers)
{
CheckMemberNameDistinctFromType(member, diagnostics);
}
}
private void CheckMemberNameConflicts(DiagnosticBag diagnostics)
{
Dictionary> membersByName = GetMembersByName();
// Collisions involving indexers are handled specially.
CheckIndexerNameConflicts(diagnostics, membersByName);
// key and value will be the same object in these dictionaries.
var methodsBySignature = new Dictionary(MemberSignatureComparer.DuplicateSourceComparer);
var conversionsAsMethods = new Dictionary(MemberSignatureComparer.DuplicateSourceComparer);
var conversionsAsConversions = new HashSet(ConversionSignatureComparer.Comparer);
// SPEC: The signature of an operator must differ from the signatures of all other
// SPEC: operators declared in the same class.
// DELIBERATE SPEC VIOLATION:
// The specification does not state that a user-defined conversion reserves the names
// op_Implicit or op_Explicit, but nevertheless the native compiler does so; an attempt
// to define a field or a conflicting method with the metadata name of a user-defined
// conversion is an error. We preserve this reasonable behavior.
//
// Similarly, we treat "public static C operator +(C, C)" as colliding with
// "public static C op_Addition(C, C)". Fortunately, this behavior simply
// falls out of treating user-defined operators as ordinary methods; we do
// not need any special handling in this method.
//
// However, we must have special handling for conversions because conversions
// use a completely different rule for detecting collisions between two
// conversions: conversion signatures consist only of the source and target
// types of the conversions, and not the kind of the conversion (implicit or explicit),
// the name of the method, and so on.
//
// Therefore we must detect the following kinds of member name conflicts:
//
// 1. a method, conversion or field has the same name as a (different) field (* see note below)
// 2. a method has the same method signature as another method or conversion
// 3. a conversion has the same conversion signature as another conversion.
//
// However, we must *not* detect "a conversion has the same *method* signature
// as another conversion" because conversions are allowed to overload on
// return type but methods are not.
//
// (*) NOTE: Throughout the rest of this method I will use "field" as a shorthand for
// "non-method, non-conversion, non-type member", rather than spelling out
// "field, property or event...")
foreach (var pair in membersByName)
{
var name = pair.Key;
Symbol lastSym = GetTypeMembers(name).FirstOrDefault();
methodsBySignature.Clear();
// Conversion collisions do not consider the name of the conversion,
// so do not clear that dictionary.
foreach (var symbol in pair.Value)
{
if (symbol.Kind == SymbolKind.NamedType ||
symbol.IsAccessor() ||
symbol.IsIndexer())
{
continue;
}
// We detect the first category of conflict by running down the list of members
// of the same name, and producing an error when we discover any of the following
// "bad transitions".
//
// * a method or conversion that comes after any field (not necessarily directly)
// * a field directly following a field
// * a field directly following a method or conversion
//
// Furthermore: we do not wish to detect collisions between nested types in
// this code; that is tested elsewhere. However, we do wish to detect a collision
// between a nested type and a field, method or conversion. Therefore we
// initialize our "bad transition" detector with a type of the given name,
// if there is one. That way we also detect the transitions of "method following
// type", and so on.
//
// The "lastSym" local below is used to detect these transitions. Its value is
// one of the following:
//
// * a nested type of the given name, or
// * the first method of the given name, or
// * the most recently processed field of the given name.
//
// If either the current symbol or the "last symbol" are not methods then
// there must be a collision:
//
// * if the current symbol is not a method and the last symbol is, then
// there is a field directly following a method of the same name
// * if the current symbol is a method and the last symbol is not, then
// there is a method directly or indirectly following a field of the same name,
// or a method of the same name as a nested type.
// * if neither are methods then either we have a field directly
// following a field of the same name, or a field and a nested type of the same name.
//
if ((object)lastSym != null)
{
if (symbol.Kind != SymbolKind.Method || lastSym.Kind != SymbolKind.Method)
{
if (symbol.Kind != SymbolKind.Field || !symbol.IsImplicitlyDeclared)
{
// The type '{0}' already contains a definition for '{1}'
if (Locations.Length == 1 || IsPartial)
{
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, symbol.Locations[0], this, symbol.Name);
}
}
if (lastSym.Kind == SymbolKind.Method)
{
lastSym = symbol;
}
}
}
else
{
lastSym = symbol;
}
// That takes care of the first category of conflict; we detect the
// second and third categories as follows:
var conversion = symbol as SourceUserDefinedConversionSymbol;
var method = symbol as SourceMemberMethodSymbol;
if (!(conversion is null))
{
// Does this conversion collide *as a conversion* with any previously-seen
// conversion?
if (!conversionsAsConversions.Add(conversion))
{
// CS0557: Duplicate user-defined conversion in type 'C'
diagnostics.Add(ErrorCode.ERR_DuplicateConversionInClass, conversion.Locations[0], this);
}
else
{
// The other set might already contain a conversion which would collide
// *as a method* with the current conversion.
if (!conversionsAsMethods.ContainsKey(conversion))
{
conversionsAsMethods.Add(conversion, conversion);
}
}
// Does this conversion collide *as a method* with any previously-seen
// non-conversion method?
SourceMemberMethodSymbol previousMethod;
if (methodsBySignature.TryGetValue(conversion, out previousMethod))
{
ReportMethodSignatureCollision(diagnostics, conversion, previousMethod);
}
// Do not add the conversion to the set of previously-seen methods; that set
// is only non-conversion methods.
}
else if (!(method is null))
{
// Does this method collide *as a method* with any previously-seen
// conversion?
SourceMemberMethodSymbol previousConversion;
if (conversionsAsMethods.TryGetValue(method, out previousConversion))
{
ReportMethodSignatureCollision(diagnostics, method, previousConversion);
}
// Do not add the method to the set of previously-seen conversions.
// Does this method collide *as a method* with any previously-seen
// non-conversion method?
SourceMemberMethodSymbol previousMethod;
if (methodsBySignature.TryGetValue(method, out previousMethod))
{
ReportMethodSignatureCollision(diagnostics, method, previousMethod);
}
else
{
// We haven't seen this method before. Make a note of it in case
// we see a colliding method later.
methodsBySignature.Add(method, method);
}
}
}
}
}
// Report a name conflict; the error is reported on the location of method1.
// UNDONE: Consider adding a secondary location pointing to the second method.
private void ReportMethodSignatureCollision(DiagnosticBag diagnostics, SourceMemberMethodSymbol method1, SourceMemberMethodSymbol method2)
{
// Partial methods are allowed to collide by signature.
if (method1.IsPartial && method2.IsPartial)
{
return;
}
// If method1 is a constructor only because its return type is missing, then
// we've already produced a diagnostic for the missing return type and we suppress the
// diagnostic about duplicate signature.
if (method1.MethodKind == MethodKind.Constructor &&
((ConstructorDeclarationSyntax)method1.SyntaxRef.GetSyntax()).Identifier.ValueText != this.Name)
{
return;
}
Debug.Assert(method1.ParameterCount == method2.ParameterCount);
for (int i = 0; i < method1.ParameterCount; i++)
{
var refKind1 = method1.Parameters[i].RefKind;
var refKind2 = method2.Parameters[i].RefKind;
if (refKind1 != refKind2)
{
// '{0}' cannot define an overloaded {1} that differs only on parameter modifiers '{2}' and '{3}'
var methodKind = method1.MethodKind == MethodKind.Constructor ? MessageID.IDS_SK_CONSTRUCTOR : MessageID.IDS_SK_METHOD;
diagnostics.Add(ErrorCode.ERR_OverloadRefKind, method1.Locations[0], this, methodKind.Localize(), refKind1.ToParameterDisplayString(), refKind2.ToParameterDisplayString());
return;
}
}
// Special case: if there are two destructors, use the destructor syntax instead of "Finalize"
var methodName = (method1.MethodKind == MethodKind.Destructor && method2.MethodKind == MethodKind.Destructor) ?
"~" + this.Name :
method1.Name;
// Type '{1}' already defines a member called '{0}' with the same parameter types
diagnostics.Add(ErrorCode.ERR_MemberAlreadyExists, method1.Locations[0], methodName, this);
}
private void CheckIndexerNameConflicts(DiagnosticBag diagnostics, Dictionary> membersByName)
{
PooledHashSet? typeParameterNames = null;
if (this.Arity > 0)
{
typeParameterNames = PooledHashSet.GetInstance();
foreach (TypeParameterSymbol typeParameter in this.TypeParameters)
{
typeParameterNames.Add(typeParameter.Name);
}
}
var indexersBySignature = new Dictionary(MemberSignatureComparer.DuplicateSourceComparer);
// Note: Can't assume that all indexers are called WellKnownMemberNames.Indexer because
// they may be explicit interface implementations.
foreach (var members in membersByName.Values)
{
string? lastIndexerName = null;
indexersBySignature.Clear();
foreach (var symbol in members)
{
if (symbol.IsIndexer())
{
PropertySymbol indexer = (PropertySymbol)symbol;
CheckIndexerSignatureCollisions(
indexer,
diagnostics,
membersByName,
indexersBySignature,
ref lastIndexerName);
// Also check for collisions with type parameters, which aren't in the member map.
// NOTE: Accessors have normal names and are handled in CheckTypeParameterNameConflicts.
if (typeParameterNames != null)
{
string indexerName = indexer.MetadataName;
if (typeParameterNames.Contains(indexerName))
{
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, indexer.Locations[0], this, indexerName);
continue;
}
}
}
}
}
typeParameterNames?.Free();
}
private void CheckIndexerSignatureCollisions(
PropertySymbol indexer,
DiagnosticBag diagnostics,
Dictionary> membersByName,
Dictionary indexersBySignature,
ref string? lastIndexerName)
{
if (!indexer.IsExplicitInterfaceImplementation) //explicit implementation names are not checked
{
string indexerName = indexer.MetadataName;
if (lastIndexerName != null && lastIndexerName != indexerName)
{
// NOTE: dev10 checks indexer names by comparing each to the previous.
// For example, if indexers are declared with names A, B, A, B, then there
// will be three errors - one for each time the name is different from the
// previous one. If, on the other hand, the names are A, A, B, B, then
// there will only be one error because only one indexer has a different
// name from the previous one.
diagnostics.Add(ErrorCode.ERR_InconsistentIndexerNames, indexer.Locations[0]);
}
lastIndexerName = indexerName;
if (Locations.Length == 1 || IsPartial)
{
if (membersByName.ContainsKey(indexerName))
{
// The name of the indexer is reserved - it can only be used by other indexers.
Debug.Assert(!membersByName[indexerName].Any(SymbolExtensions.IsIndexer));
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, indexer.Locations[0], this, indexerName);
}
}
}
PropertySymbol prevIndexerBySignature;
if (indexersBySignature.TryGetValue(indexer, out prevIndexerBySignature))
{
// Type '{1}' already defines a member called '{0}' with the same parameter types
// NOTE: Dev10 prints "this" as the name of the indexer.
diagnostics.Add(ErrorCode.ERR_MemberAlreadyExists, indexer.Locations[0], SyntaxFacts.GetText(SyntaxKind.ThisKeyword), this);
}
else
{
indexersBySignature[indexer] = indexer;
}
}
private void CheckSpecialMemberErrors(DiagnosticBag diagnostics)
{
var conversions = new TypeConversions(this.ContainingAssembly.CorLibrary);
foreach (var member in this.GetMembersUnordered())
{
member.AfterAddingTypeMembersChecks(conversions, diagnostics);
}
}
private void CheckTypeParameterNameConflicts(DiagnosticBag diagnostics)
{
if (this.TypeKind == TypeKind.Delegate)
{
// Delegates do not have conflicts between their type parameter
// names and their methods; it is legal (though odd) to say
// delegate void D(Invoke x);
return;
}
if (Locations.Length == 1 || IsPartial)
{
foreach (var tp in TypeParameters)
{
foreach (var dup in GetMembers(tp.Name))
{
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, dup.Locations[0], this, tp.Name);
}
}
}
}
private void CheckAccessorNameConflicts(DiagnosticBag diagnostics)
{
// Report errors where property and event accessors
// conflict with other members of the same name.
foreach (Symbol symbol in this.GetMembersUnordered())
{
if (symbol.IsExplicitInterfaceImplementation())
{
// If there's a name conflict it will show up as a more specific
// interface implementation error.
continue;
}
switch (symbol.Kind)
{
case SymbolKind.Property:
{
var propertySymbol = (PropertySymbol)symbol;
this.CheckForMemberConflictWithPropertyAccessor(propertySymbol, getNotSet: true, diagnostics: diagnostics);
this.CheckForMemberConflictWithPropertyAccessor(propertySymbol, getNotSet: false, diagnostics: diagnostics);
break;
}
case SymbolKind.Event:
{
var eventSymbol = (EventSymbol)symbol;
this.CheckForMemberConflictWithEventAccessor(eventSymbol, isAdder: true, diagnostics: diagnostics);
this.CheckForMemberConflictWithEventAccessor(eventSymbol, isAdder: false, diagnostics: diagnostics);
break;
}
}
}
}
internal override bool KnownCircularStruct
{
get
{
if (_lazyKnownCircularStruct == (int)ThreeState.Unknown)
{
if (TypeKind != TypeKind.Struct)
{
Interlocked.CompareExchange(ref _lazyKnownCircularStruct, (int)ThreeState.False, (int)ThreeState.Unknown);
}
else
{
var diagnostics = DiagnosticBag.GetInstance();
var value = (int)CheckStructCircularity(diagnostics).ToThreeState();
if (Interlocked.CompareExchange(ref _lazyKnownCircularStruct, value, (int)ThreeState.Unknown) == (int)ThreeState.Unknown)
{
AddDeclarationDiagnostics(diagnostics);
}
Debug.Assert(value == _lazyKnownCircularStruct);
diagnostics.Free();
}
}
return _lazyKnownCircularStruct == (int)ThreeState.True;
}
}
private bool CheckStructCircularity(DiagnosticBag diagnostics)
{
Debug.Assert(TypeKind == TypeKind.Struct);
CheckFiniteFlatteningGraph(diagnostics);
return HasStructCircularity(diagnostics);
}
private bool HasStructCircularity(DiagnosticBag diagnostics)
{
foreach (var valuesByName in GetMembersByName().Values)
{
foreach (var member in valuesByName)
{
if (member.Kind != SymbolKind.Field)
{
// NOTE: don't have to check field-like events, because they can't have struct types.
continue;
}
var field = (FieldSymbol)member;
if (field.IsStatic)
{
continue;
}
var type = field.NonPointerType();
if (((object)type != null) &&
(type.TypeKind == TypeKind.Struct) &&
BaseTypeAnalysis.StructDependsOn((NamedTypeSymbol)type, this) &&
!type.IsPrimitiveRecursiveStruct()) // allow System.Int32 to contain a field of its own type
{
// If this is a backing field, report the error on the associated property.
var symbol = field.AssociatedSymbol ?? field;
if (symbol.Kind == SymbolKind.Parameter)
{
// We should stick to members for this error.
symbol = field;
}
// Struct member '{0}' of type '{1}' causes a cycle in the struct layout
diagnostics.Add(ErrorCode.ERR_StructLayoutCycle, symbol.Locations[0], symbol, type);
return true;
}
}
}
return false;
}
private void CheckForProtectedInStaticClass(DiagnosticBag diagnostics)
{
if (!IsStatic)
{
return;
}
// no protected members allowed
foreach (var valuesByName in GetMembersByName().Values)
{
foreach (var member in valuesByName)
{
if (member is TypeSymbol)
{
// Duplicate Dev10's failure to diagnose this error.
continue;
}
if (member.DeclaredAccessibility.HasProtected())
{
if (member.Kind != SymbolKind.Method || ((MethodSymbol)member).MethodKind != MethodKind.Destructor)
{
diagnostics.Add(ErrorCode.ERR_ProtectedInStatic, member.Locations[0], member);
}
}
}
}
}
private void CheckForUnmatchedOperators(DiagnosticBag diagnostics)
{
// SPEC: The true and false unary operators require pairwise declaration.
// SPEC: A compile-time error occurs if a class or struct declares one
// SPEC: of these operators without also declaring the other.
//
// SPEC DEFICIENCY: The line of the specification quoted above should say
// the same thing as the lines below: that the formal parameters of the
// paired true/false operators must match exactly. You can't do
// op true(S) and op false(S?) for example.
// SPEC: Certain binary operators require pairwise declaration. For every
// SPEC: declaration of either operator of a pair, there must be a matching
// SPEC: declaration of the other operator of the pair. Two operator
// SPEC: declarations match when they have the same return type and the same
// SPEC: type for each parameter. The following operators require pairwise
// SPEC: declaration: == and !=, > and <, >= and <=.
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.TrueOperatorName, WellKnownMemberNames.FalseOperatorName);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.EqualityOperatorName, WellKnownMemberNames.InequalityOperatorName);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.LessThanOperatorName, WellKnownMemberNames.GreaterThanOperatorName);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.LessThanOrEqualOperatorName, WellKnownMemberNames.GreaterThanOrEqualOperatorName);
// We also produce a warning if == / != is overridden without also overriding
// Equals and GetHashCode, or if Equals is overridden without GetHashCode.
CheckForEqualityAndGetHashCode(diagnostics);
}
private void CheckForUnmatchedOperator(DiagnosticBag diagnostics, string operatorName1, string operatorName2)
{
var ops1 = this.GetOperators(operatorName1);
var ops2 = this.GetOperators(operatorName2);
CheckForUnmatchedOperator(diagnostics, ops1, ops2, operatorName2);
CheckForUnmatchedOperator(diagnostics, ops2, ops1, operatorName1);
}
private static void CheckForUnmatchedOperator(
DiagnosticBag diagnostics,
ImmutableArray ops1,
ImmutableArray ops2,
string operatorName2)
{
foreach (var op1 in ops1)
{
bool foundMatch = false;
foreach (var op2 in ops2)
{
foundMatch = DoOperatorsPair(op1, op2);
if (foundMatch)
{
break;
}
}
if (!foundMatch)
{
// CS0216: The operator 'C.operator true(C)' requires a matching operator 'false' to also be defined
diagnostics.Add(ErrorCode.ERR_OperatorNeedsMatch, op1.Locations[0], op1,
SyntaxFacts.GetText(SyntaxFacts.GetOperatorKind(operatorName2)));
}
}
}
private static bool DoOperatorsPair(MethodSymbol op1, MethodSymbol op2)
{
if (op1.ParameterCount != op2.ParameterCount)
{
return false;
}
for (int p = 0; p < op1.ParameterCount; ++p)
{
if (!op1.ParameterTypesWithAnnotations[p].Equals(op2.ParameterTypesWithAnnotations[p], TypeCompareKind.AllIgnoreOptions))
{
return false;
}
}
if (!op1.ReturnType.Equals(op2.ReturnType, TypeCompareKind.AllIgnoreOptions))
{
return false;
}
return true;
}
private void CheckForEqualityAndGetHashCode(DiagnosticBag diagnostics)
{
if (this.IsInterfaceType())
{
// Interfaces are allowed to define Equals without GetHashCode if they want.
return;
}
bool hasOp = this.GetOperators(WellKnownMemberNames.EqualityOperatorName).Any() ||
this.GetOperators(WellKnownMemberNames.InequalityOperatorName).Any();
bool overridesEquals = this.TypeOverridesObjectMethod("Equals");
if (hasOp || overridesEquals)
{
bool overridesGHC = this.TypeOverridesObjectMethod("GetHashCode");
if (overridesEquals && !overridesGHC)
{
// CS0659: 'C' overrides Object.Equals(object o) but does not override Object.GetHashCode()
diagnostics.Add(ErrorCode.WRN_EqualsWithoutGetHashCode, this.Locations[0], this);
}
if (hasOp && !overridesEquals)
{
// CS0660: 'C' defines operator == or operator != but does not override Object.Equals(object o)
diagnostics.Add(ErrorCode.WRN_EqualityOpWithoutEquals, this.Locations[0], this);
}
if (hasOp && !overridesGHC)
{
// CS0661: 'C' defines operator == or operator != but does not override Object.GetHashCode()
diagnostics.Add(ErrorCode.WRN_EqualityOpWithoutGetHashCode, this.Locations[0], this);
}
}
}
private bool TypeOverridesObjectMethod(string name)
{
foreach (var method in this.GetMembers(name).OfType())
{
if (method.IsOverride && method.GetConstructedLeastOverriddenMethod(this).ContainingType.SpecialType == Microsoft.CodeAnalysis.SpecialType.System_Object)
{
return true;
}
}
return false;
}
private void CheckFiniteFlatteningGraph(DiagnosticBag diagnostics)
{
Debug.Assert(ReferenceEquals(this, this.OriginalDefinition));
if (AllTypeArgumentCount() == 0) return;
var instanceMap = new Dictionary(ReferenceEqualityComparer.Instance);
instanceMap.Add(this, this);
foreach (var m in this.GetMembersUnordered())
{
var f = m as FieldSymbol;
if (f is null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue;
var type = (NamedTypeSymbol)f.Type;
if (InfiniteFlatteningGraph(this, type, instanceMap))
{
// Struct member '{0}' of type '{1}' causes a cycle in the struct layout
diagnostics.Add(ErrorCode.ERR_StructLayoutCycle, f.Locations[0], f, type);
//this.KnownCircularStruct = true;
return;
}
}
}
private static bool InfiniteFlatteningGraph(SourceMemberContainerTypeSymbol top, NamedTypeSymbol t, Dictionary instanceMap)
{
if (!t.ContainsTypeParameter()) return false;
NamedTypeSymbol oldInstance;
var tOriginal = t.OriginalDefinition;
if (instanceMap.TryGetValue(tOriginal, out oldInstance))
{
// short circuit when we find a cycle, but only return true when the cycle contains the top struct
return (!TypeSymbol.Equals(oldInstance, t, TypeCompareKind.AllNullableIgnoreOptions)) && ReferenceEquals(tOriginal, top);
}
else
{
instanceMap.Add(tOriginal, t);
try
{
foreach (var m in t.GetMembersUnordered())
{
var f = m as FieldSymbol;
if (f is null || !f.IsStatic || f.Type.TypeKind != TypeKind.Struct) continue;
var type = (NamedTypeSymbol)f.Type;
if (InfiniteFlatteningGraph(top, type, instanceMap)) return true;
}
return false;
}
finally
{
instanceMap.Remove(tOriginal);
}
}
}
private void CheckSequentialOnPartialType(DiagnosticBag diagnostics)
{
if (!IsPartial || this.Layout.Kind != LayoutKind.Sequential)
{
return;
}
SyntaxReference? whereFoundField = null;
if (this.SyntaxReferences.Length <= 1)
{
return;
}
foreach (var syntaxRef in this.SyntaxReferences)
{
var syntax = syntaxRef.GetSyntax() as TypeDeclarationSyntax;
if (syntax == null)
{
continue;
}
foreach (var m in syntax.Members)
{
if (HasInstanceData(m))
{
if (whereFoundField != null && whereFoundField != syntaxRef)
{
diagnostics.Add(ErrorCode.WRN_SequentialOnPartialClass, Locations[0], this);
return;
}
whereFoundField = syntaxRef;
}
}
}
}
private static bool HasInstanceData(MemberDeclarationSyntax m)
{
switch (m.Kind())
{
case SyntaxKind.FieldDeclaration:
var fieldDecl = (FieldDeclarationSyntax)m;
return
!ContainsModifier(fieldDecl.Modifiers, SyntaxKind.StaticKeyword) &&
!ContainsModifier(fieldDecl.Modifiers, SyntaxKind.ConstKeyword);
case SyntaxKind.PropertyDeclaration:
// auto-property
var propertyDecl = (PropertyDeclarationSyntax)m;
return
!ContainsModifier(propertyDecl.Modifiers, SyntaxKind.StaticKeyword) &&
!ContainsModifier(propertyDecl.Modifiers, SyntaxKind.AbstractKeyword) &&
!ContainsModifier(propertyDecl.Modifiers, SyntaxKind.ExternKeyword) &&
propertyDecl.AccessorList != null &&
All(propertyDecl.AccessorList.Accessors, a => a.Body == null && a.ExpressionBody == null);
case SyntaxKind.EventFieldDeclaration:
// field-like event declaration
var eventFieldDecl = (EventFieldDeclarationSyntax)m;
return
!ContainsModifier(eventFieldDecl.Modifiers, SyntaxKind.StaticKeyword) &&
!ContainsModifier(eventFieldDecl.Modifiers, SyntaxKind.AbstractKeyword) &&
!ContainsModifier(eventFieldDecl.Modifiers, SyntaxKind.ExternKeyword);
default:
return false;
}
}
private static bool All(SyntaxList list, Func predicate) where T : CSharpSyntaxNode
{
foreach (var t in list) { if (predicate(t)) return true; };
return false;
}
private static bool ContainsModifier(SyntaxTokenList modifiers, SyntaxKind modifier)
{
foreach (var m in modifiers) { if (m.IsKind(modifier)) return true; };
return false;
}
private Dictionary> MakeAllMembers(DiagnosticBag diagnostics)
{
var membersAndInitializers = GetMembersAndInitializers();
// Most types don't have indexers. If this is one of those types,
// just reuse the dictionary we build for early attribute decoding.
// For tuples, we also need to take the slow path.
if (membersAndInitializers.IndexerDeclarations.Length == 0 && !this.IsTupleType)
{
return GetEarlyAttributeDecodingMembersDictionary();
}
// Add indexers (plus their accessors)
var indexerMembers = ArrayBuilder.GetInstance();
Binder? binder = null;
SyntaxTree? currentTree = null;
foreach (var decl in membersAndInitializers.IndexerDeclarations)
{
var syntax = (IndexerDeclarationSyntax)decl.GetSyntax();
if (binder == null || currentTree != decl.SyntaxTree)
{
currentTree = decl.SyntaxTree;
BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(currentTree);
binder = binderFactory.GetBinder(syntax);
}
var indexer = SourcePropertySymbol.Create(this, binder, syntax, diagnostics);
CheckMemberNameDistinctFromType(indexer, diagnostics);
indexerMembers.Add(indexer);
AddAccessorIfAvailable(indexerMembers, indexer.GetMethod, diagnostics, checkName: true);
AddAccessorIfAvailable(indexerMembers, indexer.SetMethod, diagnostics, checkName: true);
}
var membersByName = mergeIndexersAndNonIndexers(membersAndInitializers.NonTypeNonIndexerMembers, indexerMembers);
indexerMembers.Free();
// Merge types into the member dictionary
AddNestedTypesToDictionary(membersByName, GetTypeMembersDictionary());
return membersByName;
// Merge (already ordered) non-type, non-indexer members with (already ordered) indexer members.
static Dictionary> mergeIndexersAndNonIndexers(ImmutableArray nonIndexerMembers, ArrayBuilder indexerMembers)
{
int nonIndexerCount = nonIndexerMembers.Length;
int indexerCount = indexerMembers.Count;
var merged = ArrayBuilder.GetInstance(nonIndexerCount + indexerCount);
int nonIndexerPos = 0;
int indexerPos = 0;
while (nonIndexerPos < nonIndexerCount && indexerPos < indexerCount)
{
var nonIndexer = nonIndexerMembers[nonIndexerPos];
var indexer = indexerMembers[indexerPos];
if (LexicalOrderSymbolComparer.Instance.Compare(nonIndexer, indexer) < 0)
{
merged.Add(nonIndexer);
nonIndexerPos++;
}
else
{
merged.Add(indexer);
indexerPos++;
}
}
for (; nonIndexerPos < nonIndexerCount; nonIndexerPos++)
{
merged.Add(nonIndexerMembers[nonIndexerPos]);
}
for (; indexerPos < indexerCount; indexerPos++)
{
merged.Add(indexerMembers[indexerPos]);
}
var membersByName = merged.ToDictionary(s => s.Name, StringOrdinalComparer.Instance);
merged.Free();
return membersByName;
}
}
private static void AddNestedTypesToDictionary(Dictionary> membersByName, Dictionary> typesByName)
{
foreach (var pair in typesByName)
{
string name = pair.Key;
ImmutableArray types = pair.Value;
ImmutableArray typesAsSymbols = StaticCast.From(types);
ImmutableArray membersForName;
if (membersByName.TryGetValue(name, out membersForName))
{
membersByName[name] = membersForName.Concat(typesAsSymbols);
}
else
{
membersByName.Add(name, typesAsSymbols);
}
}
}
private class MembersAndInitializersBuilder
{
public ArrayBuilder NonTypeNonIndexerMembers { get; private set; } = ArrayBuilder.GetInstance();
public readonly ArrayBuilder> StaticInitializers = ArrayBuilder>.GetInstance();
public readonly ArrayBuilder> InstanceInitializers = ArrayBuilder>.GetInstance();
public readonly ArrayBuilder IndexerDeclarations = ArrayBuilder.GetInstance();
public int StaticSyntaxLength;
public int InstanceSyntaxLength;
public MembersAndInitializers ToReadOnlyAndFree()
{
return new MembersAndInitializers(
NonTypeNonIndexerMembers.ToImmutableAndFree(),
StaticInitializers.ToImmutableAndFree(),
InstanceInitializers.ToImmutableAndFree(),
IndexerDeclarations.ToImmutableAndFree(),
StaticSyntaxLength,
InstanceSyntaxLength);
}
public void Free()
{
NonTypeNonIndexerMembers.Free();
StaticInitializers.Free();
InstanceInitializers.Free();
IndexerDeclarations.Free();
}
internal void AddOrWrapTupleMembers(SourceMemberContainerTypeSymbol type)
{
this.NonTypeNonIndexerMembers = type.AddOrWrapTupleMembers(this.NonTypeNonIndexerMembers.ToImmutableAndFree());
}
}
private MembersAndInitializers? BuildMembersAndInitializers(DiagnosticBag diagnostics)
{
var builder = new MembersAndInitializersBuilder();
AddDeclaredNontypeMembers(builder, diagnostics);
switch (TypeKind)
{
case TypeKind.Struct:
CheckForStructBadInitializers(builder, diagnostics);
CheckForStructDefaultConstructors(builder.NonTypeNonIndexerMembers, isEnum: false, diagnostics: diagnostics);
AddSynthesizedRecordMembersIfNecessary(builder, diagnostics);
AddSynthesizedConstructorsIfNecessary(builder.NonTypeNonIndexerMembers, builder.StaticInitializers, diagnostics);
break;
case TypeKind.Enum:
CheckForStructDefaultConstructors(builder.NonTypeNonIndexerMembers, isEnum: true, diagnostics: diagnostics);
AddSynthesizedConstructorsIfNecessary(builder.NonTypeNonIndexerMembers, builder.StaticInitializers, diagnostics);
break;
case TypeKind.Class:
case TypeKind.Interface:
case TypeKind.Submission:
AddSynthesizedRecordMembersIfNecessary(builder, diagnostics);
// No additional checking required.
AddSynthesizedConstructorsIfNecessary(builder.NonTypeNonIndexerMembers, builder.StaticInitializers, diagnostics);
break;
default:
break;
}
if (IsTupleType)
{
builder.AddOrWrapTupleMembers(this);
}
// We already built the members and initializers on another thread, we might have detected that condition
// during member building on this thread and bailed, which results in incomplete data in the builder.
// In such case we have to avoid creating the instance of MemberAndInitializers since it checks the consistency
// of the data in the builder and would fail in an assertion if we tried to construct it from incomplete builder.
if (_lazyMembersAndInitializers != null)
{
builder.Free();
return null;
}
return builder.ToReadOnlyAndFree();
}
private void AddDeclaredNontypeMembers(MembersAndInitializersBuilder builder, DiagnosticBag diagnostics)
{
foreach (var decl in this.declaration.Declarations)
{
if (!decl.HasAnyNontypeMembers)
{
continue;
}
if (_lazyMembersAndInitializers != null)
{
// membersAndInitializers is already computed. no point to continue.
return;
}
var syntax = decl.SyntaxReference.GetSyntax();
switch (syntax.Kind())
{
case SyntaxKind.EnumDeclaration:
AddEnumMembers(builder, (EnumDeclarationSyntax)syntax, diagnostics);
break;
case SyntaxKind.DelegateDeclaration:
SourceDelegateMethodSymbol.AddDelegateMembers(this, builder.NonTypeNonIndexerMembers, (DelegateDeclarationSyntax)syntax, diagnostics);
break;
case SyntaxKind.NamespaceDeclaration:
// The members of a global anonymous type is in a syntax tree of a namespace declaration or a compilation unit.
AddNonTypeMembers(builder, ((NamespaceDeclarationSyntax)syntax).Members, diagnostics);
break;
case SyntaxKind.CompilationUnit:
AddNonTypeMembers(builder, ((CompilationUnitSyntax)syntax).Members, diagnostics);
break;
case SyntaxKind.ClassDeclaration:
var classDecl = (ClassDeclarationSyntax)syntax;
AddNonTypeMembers(builder, classDecl.Members, diagnostics);
break;
case SyntaxKind.InterfaceDeclaration:
AddNonTypeMembers(builder, ((InterfaceDeclarationSyntax)syntax).Members, diagnostics);
break;
case SyntaxKind.StructDeclaration:
var structDecl = (StructDeclarationSyntax)syntax;
AddNonTypeMembers(builder, structDecl.Members, diagnostics);
break;
default:
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
}
}
}
internal Binder GetBinder(CSharpSyntaxNode syntaxNode)
{
return this.DeclaringCompilation.GetBinder(syntaxNode);
}
private static void MergePartialMembers(
ArrayBuilder memberNames,
Dictionary> membersByName,
DiagnosticBag diagnostics)
{
//key and value will be the same object
var methodsBySignature = new Dictionary(MemberSignatureComparer.PartialMethodsComparer);
foreach (var name in memberNames)
{
methodsBySignature.Clear();
foreach (var symbol in membersByName[name])
{
var method = symbol as SourceMemberMethodSymbol;
if (method is null || !method.IsPartial)
{
continue; // only partial methods need to be merged
}
SourceMemberMethodSymbol prev;
if (methodsBySignature.TryGetValue(method, out prev))
{
var prevPart = (SourceOrdinaryMethodSymbol)prev;
var methodPart = (SourceOrdinaryMethodSymbol)method;
bool hasImplementation = (object?)prevPart.OtherPartOfPartial != null || prevPart.IsPartialImplementation;
bool hasDefinition = (object?)prevPart.OtherPartOfPartial != null || prevPart.IsPartialDefinition;
if (hasImplementation && methodPart.IsPartialImplementation)
{
// A partial method may not have multiple implementing declarations
diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneActual, methodPart.Locations[0]);
}
else if (hasDefinition && methodPart.IsPartialDefinition)
{
// A partial method may not have multiple defining declarations
diagnostics.Add(ErrorCode.ERR_PartialMethodOnlyOneLatent, methodPart.Locations[0]);
}
else
{
membersByName[name] = FixPartialMember(membersByName[name], prevPart, methodPart);
}
}
else
{
methodsBySignature.Add(method, method);
}
}
foreach (SourceOrdinaryMethodSymbol method in methodsBySignature.Values)
{
// partial implementations not paired with a definition
if (method.IsPartialImplementation && method.OtherPartOfPartial is null)
{
diagnostics.Add(ErrorCode.ERR_PartialMethodMustHaveLatent, method.Locations[0], method);
}
else if (!(method.OtherPartOfPartial is null) && MemberSignatureComparer.ConsideringTupleNamesCreatesDifference(method, method.OtherPartOfPartial))
{
diagnostics.Add(ErrorCode.ERR_PartialMethodInconsistentTupleNames, method.Locations[0], method, method.OtherPartOfPartial);
}
}
}
}
///
/// Fix up a partial method by combining its defining and implementing declarations, updating the array of symbols (by name),
/// and returning the combined symbol.
///
/// The symbols array containing both the latent and implementing declaration
/// One of the two declarations
/// The other declaration
/// An updated symbols array containing only one method symbol representing the two parts
private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourceOrdinaryMethodSymbol part1, SourceOrdinaryMethodSymbol part2)
{
SourceOrdinaryMethodSymbol definition;
SourceOrdinaryMethodSymbol implementation;
if (part1.IsPartialDefinition)
{
definition = part1;
implementation = part2;
}
else
{
definition = part2;
implementation = part1;
}
SourceOrdinaryMethodSymbol.InitializePartialMethodParts(definition, implementation);
// a partial method is represented in the member list by its definition part:
return Remove(symbols, implementation);
}
private static ImmutableArray Remove(ImmutableArray symbols, Symbol symbol)
{
var builder = ArrayBuilder.GetInstance();
foreach (var s in symbols)
{
if (!ReferenceEquals(s, symbol))
{
builder.Add(s);
}
}
return builder.ToImmutableAndFree();
}
///
/// Report an error if a member (other than a method) exists with the same name
/// as the property accessor, or if a method exists with the same name and signature.
///
private void CheckForMemberConflictWithPropertyAccessor(
PropertySymbol propertySymbol,
bool getNotSet,
DiagnosticBag diagnostics)
{
Debug.Assert(!propertySymbol.IsExplicitInterfaceImplementation); // checked by caller
MethodSymbol accessor = getNotSet ? propertySymbol.GetMethod : propertySymbol.SetMethod;
string accessorName;
if ((object)accessor != null)
{
accessorName = accessor.Name;
}
else
{
string propertyName = propertySymbol.IsIndexer ? propertySymbol.MetadataName : propertySymbol.Name;
accessorName = SourcePropertyAccessorSymbol.GetAccessorName(propertyName,
getNotSet,
propertySymbol.IsCompilationOutputWinMdObj());
}
foreach (var symbol in GetMembers(accessorName))
{
if (symbol.Kind != SymbolKind.Method)
{
// The type '{0}' already contains a definition for '{1}'
if (Locations.Length == 1 || IsPartial)
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, GetAccessorOrPropertyLocation(propertySymbol, getNotSet), this, accessorName);
return;
}
else
{
var methodSymbol = (MethodSymbol)symbol;
if ((methodSymbol.MethodKind == MethodKind.Ordinary) &&
ParametersMatchPropertyAccessor(propertySymbol, getNotSet, methodSymbol.Parameters))
{
// Type '{1}' already reserves a member called '{0}' with the same parameter types
diagnostics.Add(ErrorCode.ERR_MemberReserved, GetAccessorOrPropertyLocation(propertySymbol, getNotSet), accessorName, this);
return;
}
}
}
}
///
/// Report an error if a member (other than a method) exists with the same name
/// as the event accessor, or if a method exists with the same name and signature.
///
private void CheckForMemberConflictWithEventAccessor(
EventSymbol eventSymbol,
bool isAdder,
DiagnosticBag diagnostics)
{
Debug.Assert(!eventSymbol.IsExplicitInterfaceImplementation); // checked by caller
string accessorName = SourceEventSymbol.GetAccessorName(eventSymbol.Name, isAdder);
foreach (var symbol in GetMembers(accessorName))
{
if (symbol.Kind != SymbolKind.Method)
{
// The type '{0}' already contains a definition for '{1}'
if (Locations.Length == 1 || IsPartial)
diagnostics.Add(ErrorCode.ERR_DuplicateNameInClass, GetAccessorOrEventLocation(eventSymbol, isAdder), this, accessorName);
return;
}
else
{
var methodSymbol = (MethodSymbol)symbol;
if ((methodSymbol.MethodKind == MethodKind.Ordinary) &&
ParametersMatchEventAccessor(eventSymbol, methodSymbol.Parameters))
{
// Type '{1}' already reserves a member called '{0}' with the same parameter types
diagnostics.Add(ErrorCode.ERR_MemberReserved, GetAccessorOrEventLocation(eventSymbol, isAdder), accessorName, this);
return;
}
}
}
}
///
/// Return the location of the accessor, or if no accessor, the location of the property.
///
private static Location GetAccessorOrPropertyLocation(PropertySymbol propertySymbol, bool getNotSet)
{
var locationFrom = (Symbol)(getNotSet ? propertySymbol.GetMethod : propertySymbol.SetMethod) ?? propertySymbol;
return locationFrom.Locations[0];
}
///
/// Return the location of the accessor, or if no accessor, the location of the event.
///
private static Location GetAccessorOrEventLocation(EventSymbol propertySymbol, bool isAdder)
{
var locationFrom = (Symbol)(isAdder ? propertySymbol.AddMethod : propertySymbol.RemoveMethod) ?? propertySymbol;
return locationFrom.Locations[0];
}
///
/// Return true if the method parameters match the parameters of the
/// property accessor, including the value parameter for the setter.
///
private static bool ParametersMatchPropertyAccessor(PropertySymbol propertySymbol, bool getNotSet, ImmutableArray methodParams)
{
var propertyParams = propertySymbol.Parameters;
var numParams = propertyParams.Length + (getNotSet ? 0 : 1);
if (numParams != methodParams.Length)
{
return false;
}
for (int i = 0; i < numParams; i++)
{
var methodParam = methodParams[i];
if (methodParam.RefKind != RefKind.None)
{
return false;
}
var propertyParamType = (((i == numParams - 1) && !getNotSet) ? propertySymbol.TypeWithAnnotations : propertyParams[i].TypeWithAnnotations).Type;
if (!propertyParamType.Equals(methodParam.Type, TypeCompareKind.AllIgnoreOptions))
{
return false;
}
}
return true;
}
///
/// Return true if the method parameters match the parameters of the
/// event accessor, including the value parameter.
///
private static bool ParametersMatchEventAccessor(EventSymbol eventSymbol, ImmutableArray methodParams)
{
return
methodParams.Length == 1 &&
methodParams[0].RefKind == RefKind.None &&
eventSymbol.Type.Equals(methodParams[0].Type, TypeCompareKind.AllIgnoreOptions);
}
private void AddEnumMembers(MembersAndInitializersBuilder result, EnumDeclarationSyntax syntax, DiagnosticBag diagnostics)
{
// The previous enum constant used to calculate subsequent
// implicit enum constants. (This is the most recent explicit
// enum constant or the first implicit constant if no explicit values.)
SourceEnumConstantSymbol? otherSymbol = null;
// Offset from "otherSymbol".
int otherSymbolOffset = 0;
foreach (var member in syntax.Members)
{
SourceEnumConstantSymbol symbol;
var valueOpt = member.EqualsValue;
if (valueOpt != null)
{
symbol = SourceEnumConstantSymbol.CreateExplicitValuedConstant(this, member, diagnostics);
}
else
{
symbol = SourceEnumConstantSymbol.CreateImplicitValuedConstant(this, member, otherSymbol, otherSymbolOffset, diagnostics);
}
result.NonTypeNonIndexerMembers.Add(symbol);
if (valueOpt != null || otherSymbol is null)
{
otherSymbol = symbol;
otherSymbolOffset = 1;
}
else
{
otherSymbolOffset++;
}
}
}
private static void AddInitializer(ref ArrayBuilder? initializers, ref int aggregateSyntaxLength, FieldSymbol? fieldOpt, CSharpSyntaxNode node)
{
if (initializers == null)
{
initializers = new ArrayBuilder();
}
else
{
// initializers should be added in syntax order:
Debug.Assert(node.SyntaxTree == initializers.Last().Syntax.SyntaxTree);
Debug.Assert(node.SpanStart > initializers.Last().Syntax.GetSyntax().SpanStart);
}
int currentLength = aggregateSyntaxLength;
// A constant field of type decimal needs a field initializer, so
// check if it is a metadata constant, not just a constant to exclude
// decimals. Other constants do not need field initializers.
if (fieldOpt == null || !fieldOpt.IsMetadataConstant)
{
// ignore leading and trailing trivia of the node:
aggregateSyntaxLength += node.Span.Length;
}
initializers.Add(new FieldOrPropertyInitializer(fieldOpt, node, currentLength));
}
private static void AddInitializers(
ArrayBuilder> allInitializers,
ArrayBuilder? siblingsOpt)
{
if (siblingsOpt != null)
{
allInitializers.Add(siblingsOpt.ToImmutableAndFree());
}
}
private static void CheckInterfaceMembers(ImmutableArray nonTypeMembers, DiagnosticBag diagnostics)
{
foreach (var member in nonTypeMembers)
{
CheckInterfaceMember(member, diagnostics);
}
}
private static void CheckInterfaceMember(Symbol member, DiagnosticBag diagnostics)
{
switch (member.Kind)
{
case SymbolKind.Field:
break;
case SymbolKind.Method:
var meth = (MethodSymbol)member;
switch (meth.MethodKind)
{
case MethodKind.Constructor:
diagnostics.Add(ErrorCode.ERR_InterfacesCantContainConstructors, member.Locations[0]);
break;
case MethodKind.Conversion:
diagnostics.Add(ErrorCode.ERR_InterfacesCantContainConversionOrEqualityOperators, member.Locations[0]);
break;
case MethodKind.UserDefinedOperator:
if (meth.Name == WellKnownMemberNames.EqualityOperatorName || meth.Name == WellKnownMemberNames.InequalityOperatorName)
{
diagnostics.Add(ErrorCode.ERR_InterfacesCantContainConversionOrEqualityOperators, member.Locations[0]);
}
break;
case MethodKind.Destructor:
diagnostics.Add(ErrorCode.ERR_OnlyClassesCanContainDestructors, member.Locations[0]);
break;
case MethodKind.ExplicitInterfaceImplementation:
//CS0541 is handled in SourcePropertySymbol
case MethodKind.Ordinary:
case MethodKind.LocalFunction:
case MethodKind.PropertyGet:
case MethodKind.PropertySet:
case MethodKind.EventAdd:
case MethodKind.EventRemove:
case MethodKind.StaticConstructor:
break;
default:
throw ExceptionUtilities.UnexpectedValue(meth.MethodKind);
}
break;
case SymbolKind.Property:
break;
case SymbolKind.Event:
break;
default:
throw ExceptionUtilities.UnexpectedValue(member.Kind);
}
}
private static void CheckForStructDefaultConstructors(
ArrayBuilder members,
bool isEnum,
DiagnosticBag diagnostics)
{
foreach (var s in members)
{
var m = s as MethodSymbol;
if (!(m is null))
{
if (m.MethodKind == MethodKind.Constructor && m.ParameterCount == 0)
{
if (isEnum)
{
diagnostics.Add(ErrorCode.ERR_EnumsCantContainDefaultConstructor, m.Locations[0]);
}
else
{
diagnostics.Add(ErrorCode.ERR_StructsCantContainDefaultConstructor, m.Locations[0]);
}
}
}
}
}
private void CheckForStructBadInitializers(MembersAndInitializersBuilder builder, DiagnosticBag diagnostics)
{
Debug.Assert(TypeKind == TypeKind.Struct);
foreach (var initializers in builder.InstanceInitializers)
{
foreach (FieldOrPropertyInitializer initializer in initializers)
{
// '{0}': cannot have instance field initializers in structs
diagnostics.Add(ErrorCode.ERR_FieldInitializerInStruct, (initializer.FieldOpt.AssociatedSymbol ?? initializer.FieldOpt).Locations[0], this);
}
}
}
private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilder builder, DiagnosticBag diagnostics)
{
switch (declaration.Kind)
{
case DeclarationKind.Class:
case DeclarationKind.Struct:
break;
default:
return;
}
var members = builder.NonTypeNonIndexerMembers;
ParameterListSyntax? paramList = null;
foreach (SingleTypeDeclaration decl in declaration.Declarations)
{
var syntaxNode = decl.SyntaxReference.GetSyntax();
ParameterListSyntax? curParamList = null;
switch (declaration.Kind)
{
case DeclarationKind.Class:
var classDecl = (ClassDeclarationSyntax)syntaxNode;
curParamList = classDecl.ParameterList;
break;
case DeclarationKind.Struct:
var structDecl = (StructDeclarationSyntax)syntaxNode;
curParamList = structDecl.ParameterList;
break;
}
if (paramList is null)
{
paramList = curParamList;
}
else
{
// PROTOTYPE: Add error for multiple parameter lists
}
}
if (paramList is null)
{
if (_declModifiers.HasFlag(DeclarationModifiers.Data))
{
diagnostics.Add(ErrorCode.ERR_BadRecordDeclaration, declaration.NameLocations[0]);
}
return;
}
if (!_declModifiers.HasFlag(DeclarationModifiers.Data))
{
diagnostics.Add(ErrorCode.ERR_BadRecordDeclaration, paramList.Location);
}
BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree);
var binder = binderFactory.GetBinder(paramList);
var memberSignatures = new HashSet(members, MemberSignatureComparer.DuplicateSourceComparer);
var ctor = new SynthesizedRecordConstructor(this, binder, paramList, diagnostics);
if (!memberSignatures.Contains(ctor))
{
members.Add(ctor);
}
else
{
diagnostics.Add(ErrorCode.ERR_DuplicateRecordConstructor, paramList.Location);
}
foreach (ParameterSymbol param in ctor.Parameters)
{
var property = new SynthesizedRecordPropertySymbol(this, param);
if (!memberSignatures.Contains(property))
{
members.Add(property);
members.Add(property.GetMethod);
members.Add(property.BackingField);
}
}
}
private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder members, ArrayBuilder> staticInitializers, DiagnosticBag diagnostics)
{
switch (declaration.Kind)
{
case DeclarationKind.Class:
case DeclarationKind.Struct:
break;
}
//we're not calling the helpers on NamedTypeSymbol base, because those call
//GetMembers and we're inside a GetMembers call ourselves (i.e. stack overflow)
var hasInstanceConstructor = false;
var hasParameterlessInstanceConstructor = false;
var hasStaticConstructor = false;
// CONSIDER: if this traversal becomes a bottleneck, the flags could be made outputs of the
// dictionary construction process. For now, this is more encapsulated.
foreach (var member in members)
{
if (member.Kind == SymbolKind.Method)
{
var method = (MethodSymbol)member;
switch (method.MethodKind)
{
case MethodKind.Constructor:
hasInstanceConstructor = true;
hasParameterlessInstanceConstructor = hasParameterlessInstanceConstructor || method.ParameterCount == 0;
break;
case MethodKind.StaticConstructor:
hasStaticConstructor = true;
break;
}
}
//kick out early if we've seen everything we're looking for
if (hasInstanceConstructor && hasStaticConstructor)
{
break;
}
}
// NOTE: Per section 11.3.8 of the spec, "every struct implicitly has a parameterless instance constructor".
// We won't insert a parameterless constructor for a struct if there already is one.
// We don't expect anything to be emitted, but it should be in the symbol table.
if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface))
{
members.Add((this.TypeKind == TypeKind.Submission) ?
new SynthesizedSubmissionConstructor(this, diagnostics) :
new SynthesizedInstanceConstructor(this));
}
// constants don't count, since they do not exist as fields at runtime
// NOTE: even for decimal constants (which require field initializers),
// we do not create .cctor here since a static constructor implicitly created for a decimal
// should not appear in the list returned by public API like GetMembers().
if (!hasStaticConstructor && HasNonConstantInitializer(staticInitializers))
{
// Note: we don't have to put anything in the method - the binder will
// do that when processing field initializers.
members.Add(new SynthesizedStaticConstructor(this));
}
if (this.IsScriptClass)
{
var scriptInitializer = new SynthesizedInteractiveInitializerMethod(this, diagnostics);
members.Add(scriptInitializer);
var scriptEntryPoint = SynthesizedEntryPointSymbol.Create(scriptInitializer, diagnostics);
members.Add(scriptEntryPoint);
}
}
private static bool HasNonConstantInitializer(ArrayBuilder> initializers)
{
return initializers.Any(siblings => siblings.Any(initializer => !initializer.FieldOpt.IsConst));
}
private void AddNonTypeMembers(
MembersAndInitializersBuilder builder,
SyntaxList members,
DiagnosticBag diagnostics)
{
if (members.Count == 0)
{
return;
}
var firstMember = members[0];
var bodyBinder = this.GetBinder(firstMember);
ArrayBuilder? staticInitializers = null;
ArrayBuilder? instanceInitializers = null;
foreach (var m in members)
{
if (_lazyMembersAndInitializers != null)
{
// membersAndInitializers is already computed. no point to continue.
return;
}
bool reportMisplacedGlobalCode = !m.HasErrors;
switch (m.Kind())
{
case SyntaxKind.FieldDeclaration:
{
var fieldSyntax = (FieldDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(fieldSyntax.Declaration.Variables.First().Identifier));
}
bool modifierErrors;
var modifiers = SourceMemberFieldSymbol.MakeModifiers(this, fieldSyntax.Declaration.Variables[0].Identifier, fieldSyntax.Modifiers, diagnostics, out modifierErrors);
foreach (var variable in fieldSyntax.Declaration.Variables)
{
var fieldSymbol = (modifiers & DeclarationModifiers.Fixed) == 0
? new SourceMemberFieldSymbolFromDeclarator(this, variable, modifiers, modifierErrors, diagnostics)
: new SourceFixedFieldSymbol(this, variable, modifiers, modifierErrors, diagnostics);
builder.NonTypeNonIndexerMembers.Add(fieldSymbol);
if (IsScriptClass)
{
// also gather expression-declared variables from the bracketed argument lists and the initializers
ExpressionFieldFinder.FindExpressionVariables(builder.NonTypeNonIndexerMembers, variable, this,
DeclarationModifiers.Private | (modifiers & DeclarationModifiers.Static),
fieldSymbol);
}
if (variable.Initializer != null)
{
if (fieldSymbol.IsStatic)
{
AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, fieldSymbol, variable.Initializer);
}
else
{
AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, fieldSymbol, variable.Initializer);
}
}
}
}
break;
case SyntaxKind.MethodDeclaration:
{
var methodSyntax = (MethodDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(methodSyntax.Identifier));
}
var method = SourceOrdinaryMethodSymbol.CreateMethodSymbol(this, bodyBinder, methodSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(method);
}
break;
case SyntaxKind.ConstructorDeclaration:
{
var constructorSyntax = (ConstructorDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(constructorSyntax.Identifier));
}
var constructor = SourceConstructorSymbol.CreateConstructorSymbol(this, constructorSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(constructor);
}
break;
case SyntaxKind.DestructorDeclaration:
{
var destructorSyntax = (DestructorDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(destructorSyntax.Identifier));
}
// CONSIDER: if this doesn't (directly or indirectly) override object.Finalize, the
// runtime won't consider it a finalizer and it will not be marked as a destructor
// when it is loaded from metadata. Perhaps we should just treat it as an Ordinary
// method in such cases?
var destructor = new SourceDestructorSymbol(this, destructorSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(destructor);
}
break;
case SyntaxKind.PropertyDeclaration:
{
var propertySyntax = (PropertyDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(propertySyntax.Identifier));
}
var property = SourcePropertySymbol.Create(this, bodyBinder, propertySyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(property);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, property.GetMethod, diagnostics);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, property.SetMethod, diagnostics);
FieldSymbol backingField = property.BackingField;
// TODO: can we leave this out of the member list?
// From the 10/12/11 design notes:
// In addition, we will change autoproperties to behavior in
// a similar manner and make the autoproperty fields private.
if ((object)backingField != null)
{
builder.NonTypeNonIndexerMembers.Add(backingField);
var initializer = propertySyntax.Initializer;
if (initializer != null)
{
if (IsScriptClass)
{
// also gather expression-declared variables from the initializer
ExpressionFieldFinder.FindExpressionVariables(builder.NonTypeNonIndexerMembers,
initializer,
this,
DeclarationModifiers.Private | (property.IsStatic ? DeclarationModifiers.Static : 0),
backingField);
}
if (property.IsStatic)
{
AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, backingField, initializer);
}
else
{
AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, backingField, initializer);
}
}
}
}
break;
case SyntaxKind.EventFieldDeclaration:
{
var eventFieldSyntax = (EventFieldDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(
ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(eventFieldSyntax.Declaration.Variables.First().Identifier));
}
foreach (VariableDeclaratorSyntax declarator in eventFieldSyntax.Declaration.Variables)
{
SourceFieldLikeEventSymbol @event = new SourceFieldLikeEventSymbol(this, bodyBinder, eventFieldSyntax.Modifiers, declarator, diagnostics);
builder.NonTypeNonIndexerMembers.Add(@event);
FieldSymbol associatedField = @event.AssociatedField;
if (IsScriptClass)
{
// also gather expression-declared variables from the bracketed argument lists and the initializers
ExpressionFieldFinder.FindExpressionVariables(builder.NonTypeNonIndexerMembers, declarator, this,
DeclarationModifiers.Private | (@event.IsStatic ? DeclarationModifiers.Static : 0),
associatedField);
}
if ((object)associatedField != null)
{
// NOTE: specifically don't add the associated field to the members list
// (regard it as an implementation detail).
if (declarator.Initializer != null)
{
if (associatedField.IsStatic)
{
AddInitializer(ref staticInitializers, ref builder.StaticSyntaxLength, associatedField, declarator.Initializer);
}
else
{
AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, associatedField, declarator.Initializer);
}
}
}
Debug.Assert((object)@event.AddMethod != null);
Debug.Assert((object)@event.RemoveMethod != null);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics);
}
}
break;
case SyntaxKind.EventDeclaration:
{
var eventSyntax = (EventDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(eventSyntax.Identifier));
}
var @event = new SourceCustomEventSymbol(this, bodyBinder, eventSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(@event);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.AddMethod, diagnostics);
AddAccessorIfAvailable(builder.NonTypeNonIndexerMembers, @event.RemoveMethod, diagnostics);
Debug.Assert((object)@event.AssociatedField == null);
}
break;
case SyntaxKind.IndexerDeclaration:
{
var indexerSyntax = (IndexerDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(indexerSyntax.ThisKeyword));
}
// We can't create the indexer symbol yet, because we don't know
// what name it will have after attribute binding (because of
// IndexerNameAttribute). Instead, we'll keep a (weak) reference
// to the syntax and bind it again after early attribute decoding.
builder.IndexerDeclarations.Add(indexerSyntax.GetReference());
}
break;
case SyntaxKind.ConversionOperatorDeclaration:
{
var conversionOperatorSyntax = (ConversionOperatorDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(conversionOperatorSyntax.OperatorKeyword));
}
var method = SourceUserDefinedConversionSymbol.CreateUserDefinedConversionSymbol
(this, conversionOperatorSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(method);
}
break;
case SyntaxKind.OperatorDeclaration:
{
var operatorSyntax = (OperatorDeclarationSyntax)m;
if (IsImplicitClass && reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_NamespaceUnexpected,
new SourceLocation(operatorSyntax.OperatorKeyword));
}
var method = SourceUserDefinedOperatorSymbol.CreateUserDefinedOperatorSymbol
(this, operatorSyntax, diagnostics);
builder.NonTypeNonIndexerMembers.Add(method);
}
break;
case SyntaxKind.GlobalStatement:
{
var globalStatement = ((GlobalStatementSyntax)m).Statement;
if (IsScriptClass)
{
var innerStatement = globalStatement;
// drill into any LabeledStatements
while (innerStatement.Kind() == SyntaxKind.LabeledStatement)
{
innerStatement = ((LabeledStatementSyntax)innerStatement).Statement;
}
switch (innerStatement.Kind())
{
case SyntaxKind.LocalDeclarationStatement:
// We shouldn't reach this place, but field declarations preceded with a label end up here.
// This is tracked by https://github.com/dotnet/roslyn/issues/13712. Let's do our best for now.
var decl = (LocalDeclarationStatementSyntax)innerStatement;
foreach (var vdecl in decl.Declaration.Variables)
{
// also gather expression-declared variables from the bracketed argument lists and the initializers
ExpressionFieldFinder.FindExpressionVariables(builder.NonTypeNonIndexerMembers, vdecl, this, DeclarationModifiers.Private,
containingFieldOpt: null);
}
break;
case SyntaxKind.ExpressionStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.LockStatement:
ExpressionFieldFinder.FindExpressionVariables(builder.NonTypeNonIndexerMembers,
innerStatement,
this,
DeclarationModifiers.Private,
containingFieldOpt: null);
break;
default:
// no other statement introduces variables into the enclosing scope
break;
}
AddInitializer(ref instanceInitializers, ref builder.InstanceSyntaxLength, null, globalStatement);
}
else if (reportMisplacedGlobalCode)
{
diagnostics.Add(ErrorCode.ERR_GlobalStatement, new SourceLocation(globalStatement));
}
}
break;
default:
Debug.Assert(
SyntaxFacts.IsTypeDeclaration(m.Kind()) ||
m.Kind() == SyntaxKind.NamespaceDeclaration ||
m.Kind() == SyntaxKind.IncompleteMember);
break;
}
}
AddInitializers(builder.InstanceInitializers, instanceInitializers);
AddInitializers(builder.StaticInitializers, staticInitializers);
}
private void AddAccessorIfAvailable(ArrayBuilder symbols, MethodSymbol? accessorOpt, DiagnosticBag diagnostics, bool checkName = false)
{
if (!(accessorOpt is null))
{
symbols.Add(accessorOpt);
if (checkName)
{
CheckMemberNameDistinctFromType(accessorOpt, diagnostics);
}
}
}
internal override byte? GetLocalNullableContextValue()
{
byte? value;
if (!_flags.TryGetNullableContext(out value))
{
value = ComputeNullableContextValue();
_flags.SetNullableContext(value);
}
return value;
}
private byte? ComputeNullableContextValue()
{
var compilation = DeclaringCompilation;
if (!compilation.ShouldEmitNullableAttributes(this))
{
return null;
}
var builder = new MostCommonNullableValueBuilder();
var baseType = BaseTypeNoUseSiteDiagnostics;
if (baseType is object)
{
builder.AddValue(TypeWithAnnotations.Create(baseType));
}
foreach (var @interface in GetInterfacesToEmit())
{
builder.AddValue(TypeWithAnnotations.Create(@interface));
}
foreach (var typeParameter in TypeParameters)
{
typeParameter.GetCommonNullableValues(compilation, ref builder);
}
foreach (var member in GetMembersUnordered())
{
member.GetCommonNullableValues(compilation, ref builder);
}
// Not including lambdas or local functions.
return builder.MostCommonValue;
}
internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes)
{
base.AddSynthesizedAttributes(moduleBuilder, ref attributes);
var compilation = DeclaringCompilation;
NamedTypeSymbol baseType = this.BaseTypeNoUseSiteDiagnostics;
if (baseType is object)
{
if (baseType.ContainsDynamic())
{
AddSynthesizedAttribute(ref attributes, compilation.SynthesizeDynamicAttribute(baseType, customModifiersCount: 0));
}
if (baseType.ContainsTupleNames())
{
AddSynthesizedAttribute(ref attributes, compilation.SynthesizeTupleNamesAttribute(baseType));
}
}
if (compilation.ShouldEmitNullableAttributes(this))
{
if (ShouldEmitNullableContextValue(out byte nullableContextValue))
{
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeNullableContextAttribute(this, nullableContextValue));
}
if (baseType is object)
{
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeNullableAttributeIfNecessary(this, nullableContextValue, TypeWithAnnotations.Create(baseType)));
}
}
}
#endregion
#region Extension Methods
internal bool ContainsExtensionMethods
{
get
{
if (!_lazyContainsExtensionMethods.HasValue())
{
bool containsExtensionMethods = ((this.IsStatic && !this.IsGenericType) || this.IsScriptClass) && this.declaration.ContainsExtensionMethods;
_lazyContainsExtensionMethods = containsExtensionMethods.ToThreeState();
}
return _lazyContainsExtensionMethods.Value();
}
}
internal bool AnyMemberHasAttributes
{
get
{
if (!_lazyAnyMemberHasAttributes.HasValue())
{
bool anyMemberHasAttributes = this.declaration.AnyMemberHasAttributes;
_lazyAnyMemberHasAttributes = anyMemberHasAttributes.ToThreeState();
}
return _lazyAnyMemberHasAttributes.Value();
}
}
public override bool MightContainExtensionMethods
{
get
{
return this.ContainsExtensionMethods;
}
}
#endregion
public sealed override NamedTypeSymbol ConstructedFrom
{
get { return this; }
}
}
}