private void AnalyzeProperty( SyntaxNodeAnalysisContext context, MutabilityInspector inspector ) { var root = context.Node as PropertyDeclarationSyntax; if (root == null) { throw new Exception("This should not happen if this function is wired up correctly"); } bool isStatic = root.Modifiers.Any(SyntaxKind.StaticKeyword); var prop = context.SemanticModel.GetDeclaredSymbol(root); if (prop == null) { // Could this happen? We are not emitting diagnostics in this case // even when the property has an annotation. return; } InspectFieldOrProperty( context, inspector, fieldOrProperty: prop, location: root.GetLocation(), isStatic: isStatic, fieldOrPropertyType: prop.Type, fieldOrPropertyName: prop.Name ); }
public void IsTypeMarkedImmutable_No_ReturnsFalse() { var type = CompileAndGetFooType("class Foo {}"); var inspector = new MutabilityInspector(type.Compilation); Assert.IsFalse(IsTypeMarkedFullyImmutable(type.Symbol)); }
public void IsTypeMarkedImmutable_Yes_ReturnsTrue() { var type = CompileAndGetFooType(@" [Objects.Immutable] class Foo {}" ); var inspector = new MutabilityInspector(type.Compilation); Assert.IsTrue(IsTypeMarkedFullyImmutable(type.Symbol)); }
private void AnalyzeClassOrStruct( SyntaxNodeAnalysisContext context, MutabilityInspector inspector ) { // TypeDeclarationSyntax is the base class of // ClassDeclarationSyntax and StructDeclarationSyntax var root = (TypeDeclarationSyntax)context.Node; var symbol = context.SemanticModel .GetDeclaredSymbol(root); // skip classes not marked immutable var scope = symbol.GetImmutabilityScope(); if (scope == ImmutabilityScope.None) { return; } var mutabilityResult = inspector.InspectConcreteType( symbol, MutabilityInspectionFlags.IgnoreImmutabilityAttribute // we're _validating_ the attribute ); if (mutabilityResult.IsMutable) { var reason = m_resultFormatter.Format(mutabilityResult); var location = GetLocationOfClassIdentifierAndGenericParameters(root); var diagnostic = Diagnostic.Create( Diagnostics.ImmutableClassIsnt, location, reason ); context.ReportDiagnostic(diagnostic); } else if (mutabilityResult.SeenUnauditedReasons.Count > 0) { ImmutableHashSet <string> immutableExceptions = symbol.GetAllImmutableExceptions(); if (!mutabilityResult.SeenUnauditedReasons.IsSubsetOf(immutableExceptions)) { string missingExceptions = string.Join(", ", mutabilityResult.SeenUnauditedReasons.Except(immutableExceptions)); var location = GetLocationOfClassIdentifierAndGenericParameters(root); var diagnostic = Diagnostic.Create( Diagnostics.InvalidUnauditedReasonInImmutable, location, missingExceptions ); context.ReportDiagnostic(diagnostic); } } }
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); }
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_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_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 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 IsTypeMarkedImmutable_InterfaceIs_ReturnsTrue() { var type = CompileAndGetFooType(@" class Foo : IFoo {} [Objects.Immutable] interface IFoo {}" ); var inspector = new MutabilityInspector(type.Compilation); // we have multiple types defined, so ensure that we're asserting on the correct one first. Assert.AreEqual("Foo", type.Symbol.MetadataName); Assert.IsTrue(IsTypeMarkedFullyImmutable(type.Symbol)); }
private void RegisterAnalysis(CompilationStartAnalysisContext context) { var inspector = new MutabilityInspector(context.Compilation); context.RegisterSyntaxNodeAction( ctx => AnalyzeClassOrStruct(ctx, inspector), SyntaxKind.ClassDeclaration ); context.RegisterSyntaxNodeAction( ctx => AnalyzeClassOrStruct(ctx, inspector), SyntaxKind.StructDeclaration ); }
private void RegisterAnalysis(CompilationStartAnalysisContext context) { var inspector = new MutabilityInspector(context.Compilation); context.RegisterSyntaxNodeAction( ctx => AnalyzeField(ctx, inspector), SyntaxKind.FieldDeclaration ); context.RegisterSyntaxNodeAction( ctx => AnalyzeProperty(ctx, inspector), SyntaxKind.PropertyDeclaration ); }
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); }
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); }
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_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 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 IsTypeMarkedImmutable_SomeTopLevelParentClassIs_ReturnsTrue() { var type = CompileAndGetFooType(@" class Foo : FooBase {} class FooBase : FooBaseOfBase {} [Objects.Immutable] class FooBaseOfBase { }" ); var inspector = new MutabilityInspector( type.Compilation, KnownImmutableTypes.Default ); // we have multiple types defined, so ensure that we're asserting on the correct one first. Assert.AreEqual("Foo", type.Symbol.MetadataName); Assert.IsTrue(IsTypeMarkedFullyImmutable(type.Symbol)); }
public void InspectType_LambdaInitializerReferencingField_NotMutable() { const string source = AnnotationsPreamble + @" sealed class Foo { private readonly int m_int = 3; private readonly Func<int> m_func = () => { return m_int; }; } "; TestSymbol <ITypeSymbol> ty = CompileAndGetFooType(source); var inspector = new MutabilityInspector(ty.Compilation); var result = inspector.InspectType(ty.Symbol); Assert.IsFalse(result.IsMutable); }
/// <summary> /// This helper method implements all of the shared logic. We have to /// split this for two reasons: /// /// 1. PropertyDeclarationSyntax and FieldDeclarationSyntaxs useful /// members don't come from a base class/interface so we can't do /// this generically. /// 2. Fields can define multiple variables and we may wish to output /// multiple diagnostics (individual ariables in a field declaration /// may be alright because of their initializers.) /// /// The arguments are organized roughly based on the common grammar of /// fields and properties: attributes, modifiers, type, name, /// initializer. /// </summary> private void InspectFieldOrProperty( SyntaxNodeAnalysisContext context, MutabilityInspector inspector, Location location, ISymbol fieldOrProperty, bool isStatic, bool isReadOnly, ITypeSymbol fieldOrPropertyType, string fieldOrPropertyName, ExpressionSyntax initializer, bool isProperty, bool isAutoImplementedProperty ) { var diagnostics = GatherDiagnostics( context.SemanticModel, inspector, location: location, isStatic: isStatic, isReadOnly: isReadOnly, fieldOrPropertyType: fieldOrPropertyType, fieldOrPropertyName: fieldOrPropertyName, initializationExpression: initializer, isProperty: isProperty, isAutoImplementedProperty: isAutoImplementedProperty ); // We're manually using enumerators here. // - if we used IEnumerable directly we'd re-compute the first // diagnostic in the GatherDiagnostics generator // - if we did .ToArray() early then we would avoid multiple // enumeration but would compute diagnostics even when we // ultimately ignore them due to annotations using (var diagnosticsEnumerator = diagnostics.GetEnumerator()) { ProcessDiagnostics( context, diagnosticsEnumerator, location, fieldOrProperty, fieldOrPropertyName ); } }
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 void AnalyzeField( SyntaxNodeAnalysisContext context, MutabilityInspector inspector ) { var root = context.Node as FieldDeclarationSyntax; if (root == null) { throw new Exception("This should not happen if this function is wired up correctly"); } bool isStatic = root.Modifiers.Any(SyntaxKind.StaticKeyword); foreach (var variable in root.Declaration.Variables) { var symbol = context .SemanticModel .GetDeclaredSymbol(variable) as IFieldSymbol; if (symbol == null) { // Could this happen? We are not emitting diagnostics in this // case even when the fields have an annotation. continue; } InspectFieldOrProperty( context, inspector, fieldOrProperty: symbol, location: variable.GetLocation(), isStatic: isStatic, fieldOrPropertyType: symbol.Type, fieldOrPropertyName: variable.Identifier.ValueText ); } }
/// <summary> /// All logic relating to either emitting or not emitting diagnostics other /// than the ones about unnecessary annotations belong in this function. /// This allows InspectMember to implement the logic around the unnecessary /// annotations diagnostic. Any time we bail early in AnalyzeField or /// AnalyzeProperty we risk not emitting unnecessary annotation /// diagnostics. /// </summary> private IEnumerable <Diagnostic> GatherDiagnostics( SemanticModel model, MutabilityInspector inspector, Location location, bool isStatic, bool isReadOnly, ITypeSymbol fieldOrPropertyType, string fieldOrPropertyName, ExpressionSyntax initializationExpression, bool isProperty, bool isAutoImplementedProperty ) { if (!isStatic) { yield break; } if (isProperty && !isAutoImplementedProperty) { // non-auto-implemented properties don't hold their own state. // We should never emit diagnostics for them. yield break; } // Auto-implemented properties should be treated similar to fields: // 1. They should not have a setter (i.e. isReadOnly) // 2. Their type should be immutable // a. Unless an initializer ensures it isn't if (!isReadOnly) { yield return(CreateDiagnostic( location, fieldOrPropertyName, fieldOrPropertyType.GetFullTypeNameWithGenericArguments(), MutabilityInspectionResult.Mutable( fieldOrPropertyName, fieldOrPropertyType.GetFullTypeNameWithGenericArguments(), MutabilityTarget.Member, MutabilityCause.IsNotReadonly ) )); // TODO: it'd probably be reasonable to not bail here. However // we need to update the unsafestatics report because it counts // the number of diagnostics that come out (it assumes at most one // error per-field.) yield break; } // Always prefer the type from the initializer if it exists because // it may be more specific. if (initializationExpression != null) { var initializerType = model.GetTypeInfo(initializationExpression).Type; // Fall back to the declaration type if we can't get a type for // the initializer. if (initializerType != null && !(initializerType is IErrorTypeSymbol)) { fieldOrPropertyType = initializerType; } } MutabilityInspectionResult result; // When we know the concrete type as in "new T()" we don't have to // be paranoid about mutable derived classes. if (initializationExpression is ObjectCreationExpressionSyntax) { result = inspector.InspectConcreteType(fieldOrPropertyType); } else { result = inspector.InspectType(fieldOrPropertyType); } if (result.IsMutable) { result = result.WithPrefixedMember(fieldOrPropertyName); yield return(CreateDiagnostic( location, fieldOrPropertyName, fieldOrPropertyType.GetFullTypeNameWithGenericArguments(), result )); } }