public void InspectType_IEnumerableGenericCollectionWithImmutableElement_ReturnsFalse() { var field = Field("private readonly System.Collections.Generic.IEnumerable<int> random"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
public void InspectType_ParenthesizedLambda_NotMutable() { var field = Field("private readonly Func<int,int> m_addTwo = ( a ) => a + 2;"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
public void InspectType_SealedClass_False() { var type = Type("sealed class foo {}"); var inspector = new MutabilityInspector(type.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(type.Symbol); AssertResultsAreEqual(expected, actual); }
public void InspectType_NullablePrimitiveType_NotMutable() { var field = Field("uint? foo"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
public void InspectType_Enum_False() { var type = Type("enum blah {}"); var inspector = new MutabilityInspector(type.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(type.Symbol); AssertResultsAreEqual(expected, actual); }
private void AssertUnauditedReasonsResult(TestSymbol <ITypeSymbol> ty, params string[] expectedUnauditedReasons) { var inspector = new MutabilityInspector(ty.Compilation); var expected = MutabilityInspectionResult.NotMutable( ImmutableHashSet.Create(expectedUnauditedReasons) ); var actual = inspector.InspectType(ty.Symbol, MutabilityInspectionFlags.IgnoreImmutabilityAttribute); AssertResultsAreEqual(expected, actual); }
public void InspectType_KnownImmutableType_False() { var field = Field("string random"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
public void InspectType_LambdaInitializedFromStaticMethod_NotMutable() { var field = Field("private readonly Func<int> m_func = () => int.Parse( \"1\" );"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
public void InspectType_SimpleLambdaMember_NotMutable() { var field = Field("private readonly Func<int> m_func = () => 1;"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
private MutabilityInspectionResult InspectClassOrStruct( ITypeSymbol type, HashSet <ITypeSymbol> typeStack ) { 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(MutabilityInspectionResult.NotMutable()); } // We have a type that is not marked immutable, is not an interface, is not an immutable container, etc.. // If it is defined in a different assembly, we might not have the metadata to correctly analyze it; so we fail. if (TypeIsFromOtherAssembly(type)) { return(MutabilityInspectionResult.MutableType(type, MutabilityCause.IsAnExternalUnmarkedType)); } ImmutableHashSet <string> .Builder seenUnauditedReasonsBuilder = ImmutableHashSet.CreateBuilder <string>(); typeStack.Add(type); try { foreach (ISymbol member in type.GetExplicitNonStaticMembers()) { var result = InspectMemberRecursive(member, typeStack); if (result.IsMutable) { return(result); } seenUnauditedReasonsBuilder.UnionWith(result.SeenUnauditedReasons); } // We descend into the base class last if (type.BaseType != null) { var baseResult = InspectConcreteType(type.BaseType, typeStack); if (baseResult.IsMutable) { return(baseResult); } seenUnauditedReasonsBuilder.UnionWith(baseResult.SeenUnauditedReasons); } } finally { typeStack.Remove(type); } return(MutabilityInspectionResult.NotMutable(seenUnauditedReasonsBuilder.ToImmutable())); }
public string Format(MutabilityInspectionResult result) { if (!result.IsMutable) { return(string.Empty); } var targetString = FormatTarget(result); var causeString = FormatCause(result); var formattedResult = $"{targetString} is {causeString}"; return(formattedResult); }
public void NonReadOnlyProperty_Diagnostic() { const string test = @" namespace test { class tests { public static int PropertyWithSetter { get; set; } } }"; AssertSingleDiagnostic( s_preamble + test, 15, 9, "PropertyWithSetter", MutabilityInspectionResult.Mutable( mutableMemberPath: "PropertyWithSetter", membersTypeName: "Widget", kind: MutabilityTarget.Member, cause: MutabilityCause.IsNotReadonly ) ); }
public void InspectType_ImmutableGenericCollectionWithValueTypeElement_ReturnsFalse() { var field = Field("private readonly System.Collections.Immutable.ImmutableArray<int> random"); var inspector = new MutabilityInspector( field.Compilation, KnownImmutableTypes.Default ); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
private void AssertSingleDiagnostic(string file, int line, int column, MutabilityInspectionResult result) { var reason = m_formatter.Format(result); var message = string.Format(Diagnostics.ImmutableClassIsnt.MessageFormat.ToString(), reason); var expected = new DiagnosticResult { Id = Diagnostics.ImmutableClassIsnt.Id, Message = message, Severity = DiagnosticSeverity.Error, Locations = new[] { new DiagnosticResultLocation("Test0.cs", line, column) } }; VerifyCSharpDiagnostic(file, expected); }
public void InspectType_ArrayType_True() { var field = Field("int[] random"); var expected = MutabilityInspectionResult.Mutable( null, "System.Int32[]", MutabilityTarget.Type, MutabilityCause.IsAnArray ); var inspector = new MutabilityInspector(field.Compilation); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
private MutabilityInspectionResult InspectConcreteType( ITypeSymbol type, HashSet <ITypeSymbol> typeStack, MutabilityInspectionFlags flags = MutabilityInspectionFlags.Default ) { if (type is IErrorTypeSymbol) { return(MutabilityInspectionResult.NotMutable()); } return(InspectType( type, flags | MutabilityInspectionFlags.AllowUnsealed, typeStack )); }
private MutabilityInspectionResult InspectProperty( IPropertySymbol property, HashSet <ITypeSymbol> typeStack ) { if (property.IsIndexer) { // Indexer properties are just glorified method syntax and dont' hold state return(MutabilityInspectionResult.NotMutable()); } var propertySyntax = GetDeclarationSyntax <PropertyDeclarationSyntax>(property); // TODO: can we do this without the syntax; with only the symbol? if (!propertySyntax.IsAutoImplemented()) { // Properties that are auto-implemented have an implicit // backing field that may be mutable. Otherwise, properties are // just sugar for getter/setter methods and don't themselves // contribute to mutability. return(MutabilityInspectionResult.NotMutable()); } if (!property.IsReadOnly) { return(MutabilityInspectionResult.MutableProperty( property, MutabilityCause.IsNotReadonly )); } if (propertySyntax.Initializer != null) { return(InspectInitializer( propertySyntax.Initializer.Value, typeStack ).WithPrefixedMember(property.Name)); } return(InspectType( property.Type, MutabilityInspectionFlags.Default, typeStack ).WithPrefixedMember(property.Name)); }
public void InspectType_Interface_True() { var type = Type("interface foo {}"); var inspector = new MutabilityInspector(type.Compilation); var expected = MutabilityInspectionResult.Mutable( null, $"{RootNamespace}.foo", MutabilityTarget.Type, MutabilityCause.IsAnInterface ); var actual = inspector.InspectType(type.Symbol); AssertResultsAreEqual(expected, actual); }
public void InspectType_LooksAtPropertiesInNonExternalType() { var prop = Property("public string random { get; set; }"); var inspector = new MutabilityInspector(prop.Compilation); var expected = MutabilityInspectionResult.Mutable( "random", "System.String", MutabilityTarget.Member, MutabilityCause.IsNotReadonly ); var actual = inspector.InspectType(prop.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
public void InspectType_TypeWithFuncProperty_ReturnsMutable() { var prop = Property("public Func<string> StringGetter { get; }"); var inspector = new MutabilityInspector(prop.Compilation); var expected = MutabilityInspectionResult.Mutable( "StringGetter", "System.Func", MutabilityTarget.Type, MutabilityCause.IsADelegate ); var actual = inspector.InspectType(prop.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
public void InspectType_LooksAtMembersInDeclaredType() { var field = Field("public string random"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.Mutable( "random", "System.String", MutabilityTarget.Member, MutabilityCause.IsNotReadonly ); var actual = inspector.InspectType(field.Symbol.ContainingType); AssertResultsAreEqual(expected, actual); }
public void InspectType_DoesNotLookAtMembersInExternalType() { var field = Field("public readonly System.Text.StringBuilder random"); var inspector = new MutabilityInspector(field.Compilation); var expected = MutabilityInspectionResult.Mutable( null, "System.Text.StringBuilder", MutabilityTarget.Type, MutabilityCause.IsAnExternalUnmarkedType ); var actual = inspector.InspectType(field.Symbol.Type); AssertResultsAreEqual(expected, actual); }
public void DocumentWithStaticImmutableCollectionField_GenericObject_Diag() { const string test = @" using System; namespace test { class Tests { public static readonly System.Collections.Immutable.ImmutableList<object> bad; } }"; AssertSingleDiagnostic( s_preamble + test, 17, 87, "bad", MutabilityInspectionResult.Mutable( mutableMemberPath: "bad", membersTypeName: "System.Object", kind: MutabilityTarget.TypeArgument, cause: MutabilityCause.IsNotSealed ) ); }
public void DocumentWithStaticCollectionField_NonGeneric_Diag() { const string test = @" using System; namespace test { class Tests { public static readonly System.Collections.IList bad; } }"; AssertSingleDiagnostic( s_preamble + test, 17, 61, "bad", MutabilityInspectionResult.Mutable( mutableMemberPath: "bad", membersTypeName: "System.Collections.IList", kind: MutabilityTarget.Type, cause: MutabilityCause.IsAnInterface ) ); }
private MutabilityInspectionResult InspectImmutableContainerType( ITypeSymbol type, HashSet <ITypeSymbol> typeStack ) { var namedType = type as INamedTypeSymbol; ImmutableHashSet <string> .Builder unauditedReasonsBuilder = ImmutableHashSet.CreateBuilder <string>(); for (int i = 0; i < namedType.TypeArguments.Length; i++) { var arg = namedType.TypeArguments[i]; var result = InspectType( arg, MutabilityInspectionFlags.Default, typeStack ); if (result.IsMutable) { if (result.Target == MutabilityTarget.Member) { // modify the result to prefix with container member. var prefix = ImmutableContainerTypes[type.GetFullTypeName()]; result = result.WithPrefixedMember(prefix[i]); } else { // modify the result to target the type argument if the // target is not a member result = result.WithTarget( MutabilityTarget.TypeArgument ); } return(result); } unauditedReasonsBuilder.UnionWith(result.SeenUnauditedReasons); } return(MutabilityInspectionResult.NotMutable(unauditedReasonsBuilder.ToImmutable())); }
public void DocumentWithStatic_ClassIsNotImmutableButImplementsImmutableInterface_Diag() { const string test = @" namespace test { [Immutable] interface IFoo {} class Test : IFoo { public DateTime bad = DateTime.Now; public DateTime badToo { get; set; } } }" ; AssertSingleDiagnostic(s_preamble + test, 13, 9, MutabilityInspectionResult.Mutable( "bad", "System.DateTime", MutabilityTarget.Member, MutabilityCause.IsNotReadonly )); }
public void InspectType_NonSealedClass_True() { var type = Type("class foo {}"); var inspector = new MutabilityInspector( type.Compilation, KnownImmutableTypes.Default ); var expected = MutabilityInspectionResult.Mutable( null, $"{RootNamespace}.foo", MutabilityTarget.Type, MutabilityCause.IsNotSealed ); var actual = inspector.InspectType(type.Symbol); AssertResultsAreEqual(expected, actual); }
public void InspectType_NullableNonPrimitiveType_NotMutable() { var type = Type(@" class Test { struct Hello { } Hello? nullable; }" ); var field = type.Symbol.GetMembers().FirstOrDefault(m => m is IFieldSymbol); Assert.IsNotNull(field); var realType = (field as IFieldSymbol).Type; var inspector = new MutabilityInspector(type.Compilation); var expected = MutabilityInspectionResult.NotMutable(); var actual = inspector.InspectType(realType); AssertResultsAreEqual(expected, actual); }
private MutabilityInspectionResult InspectMemberRecursive( ISymbol symbol, HashSet <ITypeSymbol> typeStack ) { // if the member is audited or unaudited, ignore it if (Attributes.Mutability.Audited.IsDefined(symbol)) { return(MutabilityInspectionResult.NotMutable()); } if (Attributes.Mutability.Unaudited.IsDefined(symbol)) { string unauditedReason = BecauseHelpers.GetUnauditedReason(symbol); return(MutabilityInspectionResult.NotMutable(ImmutableHashSet.Create(unauditedReason))); } switch (symbol.Kind) { case SymbolKind.Property: return(InspectProperty( symbol as IPropertySymbol, typeStack )); case SymbolKind.Field: return(InspectField( symbol as IFieldSymbol, typeStack )); case SymbolKind.Method: case SymbolKind.NamedType: // ignore these symbols, because they do not contribute to immutability return(MutabilityInspectionResult.NotMutable()); default: // we've got a member (event, etc.) that we can't currently be smart about, so fail return(MutabilityInspectionResult.PotentiallyMutableMember(symbol)); } }
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 )); }