private MutabilityInspectionResult InspectConcreteType(
            ITypeSymbol type,
            HashSet <ITypeSymbol> typeStack,
            MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default
            )
        {
            if (type is IErrorTypeSymbol)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            // The concrete type System.Object is empty (and therefore safe.)
            // For example, `private readonly object m_lock = new object();` is fine.
            if (type.SpecialType == SpecialType.System_Object)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            // System.ValueType is the base class of all value types (obscure detail)
            if (type.SpecialType == SpecialType.System_ValueType)
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            var scope = type.GetImmutabilityScope();

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && scope != ImmutabilityScope.None)
            {
                ImmutableHashSet <string> immutableExceptions = type.GetAllImmutableExceptions();
                return(MutabilityInspectionResult.NotMutable(immutableExceptions));
            }

            return(InspectType(
                       type,
                       flags | MutabilityInspectionFlags.AllowUnsealed,
                       typeStack
                       ));
        }
        private MutabilityInspectionResult InspectTypeImpl(
            ITypeSymbol type,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (type is IErrorTypeSymbol)
            {
                // This only happens for code that otherwise won't compile. Our
                // analyzer doesn't need to validate these types. It only needs
                // to be strict for valid code.
                return(MutabilityInspectionResult.NotMutable());
            }

            if (type == null)
            {
                throw new Exception("Type cannot be resolved. Please ensure all dependencies "
                                    + "are referenced, including transitive dependencies.");
            }

            // If we're not verifying immutability, we might be able to bail out early
            var scope = type.GetImmutabilityScope();

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && scope == ImmutabilityScope.SelfAndChildren)
            {
                ImmutableHashSet <string> immutableExceptions = type.GetAllImmutableExceptions();
                return(MutabilityInspectionResult.NotMutable(immutableExceptions));
            }

            if (m_knownImmutableTypes.IsTypeKnownImmutable(type))
            {
                return(MutabilityInspectionResult.NotMutable());
            }

            if (IsAnImmutableContainerType(type))
            {
                return(InspectImmutableContainerType(type, typeStack));
            }

            if (!flags.HasFlag(MutabilityInspectionFlags.AllowUnsealed) &&
                type.TypeKind == TypeKind.Class &&
                !type.IsSealed
                )
            {
                return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsNotSealed));
            }

            switch (type.TypeKind)
            {
            case TypeKind.Array:
                // Arrays are always mutable because you can rebind the
                // individual elements.
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsAnArray
                           ));

            case TypeKind.Delegate:
                // Delegates can hold state so are mutable in general.
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsADelegate
                           ));

            case TypeKind.Dynamic:
                // Dynamic types are always mutable
                return(MutabilityInspectionResult.MutableType(
                           type,
                           MutabilityCause.IsDynamic
                           ));

            case TypeKind.Enum:
                // Enums are just fancy ints.
                return(MutabilityInspectionResult.NotMutable());

            case TypeKind.TypeParameter:
                return(InspectTypeParameter(
                           type,
                           typeStack
                           ));

            case TypeKind.Interface:
                return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsAnInterface));

            case TypeKind.Class:
            case TypeKind.Struct:                     // equivalent to TypeKind.Structure
                return(InspectClassOrStruct(
                           type,
                           typeStack
                           ));

            case TypeKind.Error:
                // This only happens when the build is failing for other
                // (more fundamental) reasons. We only need to be strict
                // for otherwise-successful builds, so we bail analysis in
                // this case.
                return(MutabilityInspectionResult.NotMutable());

            case TypeKind.Unknown:
                // Looking at the Roslyn source this doesn't appear to
                // happen outside their tests. It is value 0 in the enum so
                // it may just be a safety guard.
                throw new NotImplementedException();

            default:
                // not handled: Module, Pointer, Submission.
                throw new NotImplementedException(
                          $"TypeKind.{type.Kind} not handled by analysis"
                          );
            }
        }
        private bool IsTypeMutableRecursive(
            ITypeSymbol type,
            MutabilityInspectionFlags flags,
            HashSet <ITypeSymbol> typeStack
            )
        {
            if (type is IErrorTypeSymbol || type == null)
            {
                throw new Exception($"Type '{type}' cannot be resolved. Please ensure all dependencies "
                                    + "are referenced, including transitive dependencies.");
            }

            if (IsTypeKnownImmutable(type))
            {
                return(false);
            }

            if (type.TypeKind == TypeKind.Array)
            {
                return(true);
            }

            if (!flags.HasFlag(MutabilityInspectionFlags.IgnoreImmutabilityAttribute) && IsTypeMarkedImmutable(type))
            {
                return(false);
            }

            if (typeStack.Contains(type))
            {
                // We have a cycle. If we're here, it means that either some read-only member causes the cycle (via IsMemberMutableRecursive),
                // or a generic parameter to a type causes the cycle (via IsTypeMutableRecursive). This is safe if the checks above have
                // passed and the remaining members are read-only immutable. So we can skip the current check, and allow the type to continue
                // to be evaluated.
                return(false);
            }

            typeStack.Add(type);
            try {
                if (ImmutableContainerTypes.Contains(type.GetFullTypeName()))
                {
                    var  namedType = type as INamedTypeSymbol;
                    bool isMutable = namedType.TypeArguments.Any(t => IsTypeMutableRecursive(t, MutabilityInspectionFlags.Default, typeStack));
                    return(isMutable);
                }

                if (type.TypeKind == TypeKind.Interface)
                {
                    return(true);
                }

                if (!flags.HasFlag(MutabilityInspectionFlags.AllowUnsealed) &&
                    type.TypeKind == TypeKind.Class &&
                    !type.IsSealed
                    )
                {
                    return(true);
                }

                foreach (ISymbol member in type.GetNonStaticMembers())
                {
                    if (IsMemberMutableRecursive(member, MutabilityInspectionFlags.Default, typeStack))
                    {
                        return(true);
                    }
                }

                return(false);
            } finally {
                typeStack.Remove(type);
            }
        }