| File: BoundTree\BoundNode.cs | Web Access |
| Project: src\roslyn\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] internal abstract partial class BoundNode { private readonly BoundKind _kind; private BoundNodeAttributes _attributes; /// <summary> /// Sequence points permit Syntax to be null. But all other contexts require a non-null Syntax, /// so we annotate it for the majority of uses. /// </summary> public readonly SyntaxNode Syntax; [Flags()] private enum BoundNodeAttributes : short { HasErrors = 1 << 0, CompilerGenerated = 1 << 1, IsSuppressed = 1 << 2, // Bit 3: 1 if the node has maybe-null state, 0 if the node is not null // Bits 4 and 5: 01 if the node is not annotated, 10 if the node is annotated, 11 if the node is disabled TopLevelFlowStateMaybeNull = 1 << 3, TopLevelNotAnnotated = 1 << 4, TopLevelAnnotated = 1 << 5, TopLevelNone = TopLevelAnnotated | TopLevelNotAnnotated, TopLevelAnnotationMask = TopLevelNone, /// <summary> /// Captures the fact that consumers of the node already checked the state of the WasCompilerGenerated bit. /// Allows to assert on attempts to set WasCompilerGenerated bit after that. /// </summary> WasCompilerGeneratedIsChecked = 1 << 6, WasTopLevelNullabilityChecked = 1 << 7, /// <summary> /// Captures the fact that the node was either converted to some type, or converted to its natural /// type. This is used to check the fact that every rvalue must pass through one of the two, /// so that expressions like tuple literals and switch expressions can reliably be rewritten once /// the target type is known. /// </summary> WasConverted = 1 << 8, ParamsArrayOrCollection = 1 << 9, /// <summary> /// Set after checking if the property access should use the backing field directly. /// </summary> WasPropertyBackingFieldAccessChecked = 1 << 10, AttributesPreservedInClone = HasErrors | CompilerGenerated | IsSuppressed | WasConverted | ParamsArrayOrCollection, } protected new BoundNode MemberwiseClone() { var result = (BoundNode)base.MemberwiseClone(); result._attributes &= BoundNodeAttributes.AttributesPreservedInClone; return result; } protected BoundNode(BoundKind kind, SyntaxNode syntax) { Debug.Assert( kind == BoundKind.SequencePoint || kind == BoundKind.SequencePointExpression || kind == (BoundKind)byte.MaxValue || // used in SpillSequenceSpiller syntax != null); _kind = kind; this.Syntax = syntax; } protected BoundNode(BoundKind kind, SyntaxNode syntax, bool hasErrors) : this(kind, syntax) { if (hasErrors) { _attributes = BoundNodeAttributes.HasErrors; } } /// <summary> /// Determines if a bound node, or associated syntax or type has an error (not a warning) /// diagnostic associated with it. /// /// Typically used in the binder as a way to prevent cascading errors. /// In most other cases a more lightweight HasErrors should be used. /// </summary> public bool HasAnyErrors { get { // NOTE: check Syntax rather than WasCompilerGenerated because sequence points can have null syntax. if (this.HasErrors || this.Syntax != null && this.Syntax.HasErrors) { return true; } var expression = this as BoundExpression; return expression?.Type?.IsErrorType() == true; } } /// <summary> /// Determines if a bound node, or any child, grandchild, etc has an error (not warning) /// diagnostic associated with it. The HasError bit is initially set for a node by providing it /// to the node constructor. If any child nodes of a node have /// the HasErrors bit set, then it is automatically set to true on the parent bound node. /// /// HasErrors indicates that the tree is not emittable and used to short-circuit lowering/emit stages. /// NOTE: not having HasErrors does not guarantee that we do not have any diagnostic associated /// with corresponding syntax or type. /// </summary> public bool HasErrors { get { return (_attributes & BoundNodeAttributes.HasErrors) != 0; } private set { if (value) { _attributes |= BoundNodeAttributes.HasErrors; } else { Debug.Assert((_attributes & BoundNodeAttributes.HasErrors) == 0, "HasErrors flag should not be reset here"); } } } public SyntaxTree? SyntaxTree { get { return Syntax?.SyntaxTree; } } protected void CopyAttributes(BoundNode original) { this.WasCompilerGenerated = original.WasCompilerGenerated; Debug.Assert(original is BoundExpression || !original.IsSuppressed); this.IsSuppressed = original.IsSuppressed; if (original.IsParamsArrayOrCollection) { this.IsParamsArrayOrCollection = true; } #if DEBUG this.WasConverted = original.WasConverted; #endif } /// <remarks> /// NOTE: not generally set in rewriters. /// </remarks> public bool WasCompilerGenerated { get { #if DEBUG _attributes |= BoundNodeAttributes.WasCompilerGeneratedIsChecked; #endif return (_attributes & BoundNodeAttributes.CompilerGenerated) != 0; } internal set { #if DEBUG Debug.Assert((_attributes & BoundNodeAttributes.WasCompilerGeneratedIsChecked) == 0, "compiler generated flag should not be set after reading it"); #endif if (value) { _attributes |= BoundNodeAttributes.CompilerGenerated; } else { Debug.Assert((_attributes & BoundNodeAttributes.CompilerGenerated) == 0, "compiler generated flag should not be reset here"); } } } // PERF: it is very uncommon for a flag being forcibly reset // so we do not support it in general (making the commonly used implementation simpler) // and instead have a special method to do resetting. public void ResetCompilerGenerated(bool newCompilerGenerated) { #if DEBUG Debug.Assert((_attributes & BoundNodeAttributes.WasCompilerGeneratedIsChecked) == 0, "compiler generated flag should not be set after reading it"); #endif if (newCompilerGenerated) { _attributes |= BoundNodeAttributes.CompilerGenerated; } else { _attributes &= ~BoundNodeAttributes.CompilerGenerated; } } /// <summary> /// Top level nullability for the node. This should not be used by flow analysis. /// </summary> [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected NullabilityInfo TopLevelNullability { get { #if DEBUG _attributes |= BoundNodeAttributes.WasTopLevelNullabilityChecked; #endif // This is broken out into a separate property so the debugger can display the // top level nullability without setting the _attributes flag and interfering // with the normal operation of tests. return TopLevelNullabilityCore; } set { #if DEBUG Debug.Assert((_attributes & BoundNodeAttributes.WasTopLevelNullabilityChecked) == 0, "bound node nullability should not be set after reading it"); #endif _attributes &= ~(BoundNodeAttributes.TopLevelAnnotationMask | BoundNodeAttributes.TopLevelFlowStateMaybeNull); _attributes |= value.Annotation switch { CodeAnalysis.NullableAnnotation.Annotated => BoundNodeAttributes.TopLevelAnnotated, CodeAnalysis.NullableAnnotation.NotAnnotated => BoundNodeAttributes.TopLevelNotAnnotated, CodeAnalysis.NullableAnnotation.None => BoundNodeAttributes.TopLevelNone, var a => throw ExceptionUtilities.UnexpectedValue(a), }; switch (value.FlowState) { case CodeAnalysis.NullableFlowState.MaybeNull: _attributes |= BoundNodeAttributes.TopLevelFlowStateMaybeNull; break; case CodeAnalysis.NullableFlowState.NotNull: // Not needed: unset is NotNull break; default: throw ExceptionUtilities.UnexpectedValue(value.FlowState); } } } /// <summary> /// This is for debugger display use only: <see cref="TopLevelNullability"/> will set the BoundNodeAttributes.WasTopLevelNullabilityChecked /// bit in the boundnode properties, which will break debugging. This allows the debugger to display the current value without setting the bit. /// </summary> private NullabilityInfo TopLevelNullabilityCore { get { if ((_attributes & BoundNodeAttributes.TopLevelAnnotationMask) == 0) { return default; } var annotation = (_attributes & BoundNodeAttributes.TopLevelAnnotationMask) switch { BoundNodeAttributes.TopLevelAnnotated => CodeAnalysis.NullableAnnotation.Annotated, BoundNodeAttributes.TopLevelNotAnnotated => CodeAnalysis.NullableAnnotation.NotAnnotated, BoundNodeAttributes.TopLevelNone => CodeAnalysis.NullableAnnotation.None, var mask => throw ExceptionUtilities.UnexpectedValue(mask) }; var flowState = (_attributes & BoundNodeAttributes.TopLevelFlowStateMaybeNull) == 0 ? CodeAnalysis.NullableFlowState.NotNull : CodeAnalysis.NullableFlowState.MaybeNull; return new NullabilityInfo(annotation, flowState); } } public bool IsSuppressed { get { return (_attributes & BoundNodeAttributes.IsSuppressed) != 0; } protected set { Debug.Assert((_attributes & BoundNodeAttributes.IsSuppressed) == 0, "flag should not be set twice or reset"); if (value) { _attributes |= BoundNodeAttributes.IsSuppressed; } } } #if DEBUG /// <summary> /// WasConverted flag is used for debugging purposes only (not to direct the behavior of semantic analysis). /// It is used on BoundLocal and BoundParameter to check that every such rvalue that has not been converted to /// some type has been converted to its natural type. /// </summary> public bool WasConverted { get { return (_attributes & BoundNodeAttributes.WasConverted) != 0; } protected set { Debug.Assert((_attributes & BoundNodeAttributes.WasConverted) == 0, "WasConverted flag should not be set twice or reset"); if (value) { _attributes |= BoundNodeAttributes.WasConverted; } } } public bool WasPropertyBackingFieldAccessChecked { get { return (_attributes & BoundNodeAttributes.WasPropertyBackingFieldAccessChecked) != 0; } set { Debug.Assert((_attributes & BoundNodeAttributes.WasPropertyBackingFieldAccessChecked) == 0, "should not be set twice or reset"); if (value) { _attributes |= BoundNodeAttributes.WasPropertyBackingFieldAccessChecked; } } } #endif public bool IsParamsArrayOrCollection { get { return (_attributes & BoundNodeAttributes.ParamsArrayOrCollection) != 0; } protected set { RoslynDebug.Assert((_attributes & BoundNodeAttributes.ParamsArrayOrCollection) == 0, $"{nameof(BoundNodeAttributes.ParamsArrayOrCollection)} flag should not be set twice or reset"); Debug.Assert(value || !IsParamsArrayOrCollection); Debug.Assert(!value || this is BoundArrayCreation { Bounds: [BoundLiteral { WasCompilerGenerated: true }], InitializerOpt: BoundArrayInitialization { WasCompilerGenerated: true }, WasCompilerGenerated: true } or BoundUnconvertedCollectionExpression { WasCompilerGenerated: true } or BoundCollectionExpression { WasCompilerGenerated: true, UnconvertedCollectionExpression.IsParamsArrayOrCollection: true } or BoundConversion { Operand: BoundCollectionExpression { IsParamsArrayOrCollection: true } }); Debug.Assert(!value || this is not BoundUnconvertedCollectionExpression collection || ImmutableArray<BoundNode>.CastUp(collection.Elements.CastArray<BoundExpression>()) == collection.Elements); if (value) { _attributes |= BoundNodeAttributes.ParamsArrayOrCollection; } } } public BoundKind Kind { get { return _kind; } } public virtual BoundNode? Accept(BoundTreeVisitor visitor) { throw new NotImplementedException(); } /// <summary> /// Return a clone of the current node with the HasErrors flag set. /// </summary> internal BoundNode WithHasErrors() { if (this.HasErrors) return this; BoundNode clone = MemberwiseClone(); clone.HasErrors = true; return clone; } #if DEBUG private class MyTreeDumper : TreeDumper { private MyTreeDumper() : base() { } public static new string DumpCompact(TreeDumperNode root) { return new MyTreeDumper().DoDumpCompact(root); } protected override string DumperString(object o) { return (o is SynthesizedLocal l) ? l.DumperString() : base.DumperString(o); } } internal virtual string Dump() { return MyTreeDumper.DumpCompact(BoundTreeDumperNodeProducer.MakeTree(this)); } #endif internal string GetDebuggerDisplay() { var result = GetType().Name; if (Syntax != null) { result += " " + Syntax.ToString(); } return result; } [Conditional("DEBUG")] public void CheckLocalsDefined() { #if DEBUG LocalsScanner.CheckLocalsDefined(this); #endif } public static Conversion GetConversion(BoundExpression? conversion, BoundValuePlaceholder? placeholder) { switch (conversion) { case null: return Conversion.NoConversion; case BoundConversion boundConversion: if ((object)boundConversion.Operand == placeholder) { return boundConversion.Conversion; } ConversionGroup? conversionGroupOpt = boundConversion.ConversionGroupOpt; if (conversionGroupOpt?.Conversion.IsUserDefined == true) { BoundConversion? possiblyUserDefined = boundConversion; while (possiblyUserDefined?.Conversion.IsUserDefined == false) { possiblyUserDefined = possiblyUserDefined.Operand as BoundConversion; } if (possiblyUserDefined is not null) { Debug.Assert(possiblyUserDefined.Conversion.IsUserDefined); var operand = possiblyUserDefined.Operand; while (operand is BoundConversion operandAsConversion && operandAsConversion.ConversionGroupOpt == conversionGroupOpt) { operand = operandAsConversion.Operand; } if ((object)operand == placeholder) { return possiblyUserDefined.Conversion; } } throw ExceptionUtilities.UnexpectedValue(conversion); } if (conversionGroupOpt?.Conversion.IsUnion == true) { BoundConversion? possiblyUnion = boundConversion; while (possiblyUnion?.Conversion.IsUnion == false) { possiblyUnion = possiblyUnion.Operand as BoundConversion; } if (possiblyUnion is not null) { Debug.Assert(possiblyUnion.Conversion.IsUnion); var operand = possiblyUnion.Operand; while (operand is BoundConversion operandAsConversion && operandAsConversion.ConversionGroupOpt == conversionGroupOpt) { operand = operandAsConversion.Operand; } if ((object)operand == placeholder) { return possiblyUnion.Conversion; } } throw ExceptionUtilities.UnexpectedValue(conversion); } if (!boundConversion.Conversion.IsUserDefined) { boundConversion = (BoundConversion)boundConversion.Operand; } if (boundConversion.Conversion.IsUserDefined) { Debug.Assert((boundConversion.InConversionGroupFlags & InConversionGroupFlags.LoweredFormOfUserDefinedConversionForExpressionTree) != 0); BoundConversion next; if ((object)boundConversion.Operand == placeholder || (object)(next = (BoundConversion)boundConversion.Operand).Operand == placeholder || (object)((BoundConversion)next.Operand).Operand == placeholder) { return boundConversion.Conversion; } } goto default; case BoundValuePlaceholder valuePlaceholder when (object)valuePlaceholder == placeholder: return Conversion.Identity; default: throw ExceptionUtilities.UnexpectedValue(conversion); } } #if DEBUG private class LocalsScanner : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator { public readonly PooledHashSet<LocalSymbol> DeclaredLocals = PooledHashSet<LocalSymbol>.GetInstance(); private LocalsScanner() { } public static void CheckLocalsDefined(BoundNode root) { var localsScanner = new LocalsScanner(); localsScanner.Visit(root); localsScanner.Free(); } private void AddAll(ImmutableArray<LocalSymbol> locals) { foreach (var local in locals) { if (!DeclaredLocals.Add(local)) { Debug.Assert(false, "duplicate local " + local.GetDebuggerDisplay()); } } } private void RemoveAll(ImmutableArray<LocalSymbol> locals) { foreach (var local in locals) { if (!DeclaredLocals.Remove(local)) { Debug.Assert(false, "missing local " + local.GetDebuggerDisplay()); } } } private void CheckDeclared(LocalSymbol local) { if (!DeclaredLocals.Contains(local)) { Debug.Assert(false, "undeclared local " + local.GetDebuggerDisplay()); } } public override BoundNode? VisitFieldEqualsValue(BoundFieldEqualsValue node) { AddAll(node.Locals); base.VisitFieldEqualsValue(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitPropertyEqualsValue(BoundPropertyEqualsValue node) { AddAll(node.Locals); base.VisitPropertyEqualsValue(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitParameterEqualsValue(BoundParameterEqualsValue node) { AddAll(node.Locals); base.VisitParameterEqualsValue(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitBlock(BoundBlock node) { var instrumentation = node.Instrumentation; if (instrumentation != null) { foreach (var local in instrumentation.Locals) { var added = DeclaredLocals.Add(local); Debug.Assert(added); } _ = Visit(instrumentation.Prologue); } AddAll(node.Locals); base.VisitBlock(node); RemoveAll(node.Locals); if (instrumentation != null) { _ = Visit(instrumentation.Epilogue); foreach (var local in instrumentation.Locals) { var removed = DeclaredLocals.Remove(local); Debug.Assert(removed); } } return null; } public override BoundNode? VisitLocalDeclaration(BoundLocalDeclaration node) { CheckDeclared(node.LocalSymbol); base.VisitLocalDeclaration(node); return null; } public override BoundNode? VisitSequence(BoundSequence node) { AddAll(node.Locals); base.VisitSequence(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitSpillSequence(BoundSpillSequence node) { AddAll(node.Locals); base.VisitSpillSequence(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitSwitchStatement(BoundSwitchStatement node) { AddAll(node.InnerLocals); base.VisitSwitchStatement(node); RemoveAll(node.InnerLocals); return null; } public override BoundNode? VisitSwitchExpressionArm(BoundSwitchExpressionArm node) { AddAll(node.Locals); base.VisitSwitchExpressionArm(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitSwitchSection(BoundSwitchSection node) { AddAll(node.Locals); base.VisitSwitchSection(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitDoStatement(BoundDoStatement node) { AddAll(node.Locals); base.VisitDoStatement(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitWhileStatement(BoundWhileStatement node) { AddAll(node.Locals); base.VisitWhileStatement(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitForStatement(BoundForStatement node) { AddAll(node.OuterLocals); this.Visit(node.Initializer); AddAll(node.InnerLocals); this.Visit(node.Condition); this.Visit(node.Increment); this.Visit(node.Body); RemoveAll(node.InnerLocals); RemoveAll(node.OuterLocals); return null; } public override BoundNode? VisitForEachStatement(BoundForEachStatement node) { AddAll(node.IterationVariables); base.VisitForEachStatement(node); RemoveAll(node.IterationVariables); return null; } public override BoundNode? VisitUsingStatement(BoundUsingStatement node) { AddAll(node.Locals); base.VisitUsingStatement(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitFixedStatement(BoundFixedStatement node) { AddAll(node.Locals); base.VisitFixedStatement(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitCatchBlock(BoundCatchBlock node) { AddAll(node.Locals); base.VisitCatchBlock(node); RemoveAll(node.Locals); return null; } public override BoundNode? VisitLocal(BoundLocal node) { CheckDeclared(node.LocalSymbol); base.VisitLocal(node); return null; } public override BoundNode? VisitPseudoVariable(BoundPseudoVariable node) { CheckDeclared(node.LocalSymbol); base.VisitPseudoVariable(node); return null; } public override BoundNode? VisitConstructorMethodBody(BoundConstructorMethodBody node) { AddAll(node.Locals); base.VisitConstructorMethodBody(node); RemoveAll(node.Locals); return null; } public void Free() { DeclaredLocals.Free(); } } #endif } }