diff --git a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityAnalyzer.cs b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityAnalyzer.cs index 5eb8cae09..94ce8f463 100644 --- a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityAnalyzer.cs +++ b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityAnalyzer.cs @@ -116,6 +116,10 @@ INamedTypeSymbol typeSymbol return; } + if( Attributes.Objects.ConditionallyImmutable.IsDefined( typeSymbol ) ) { + immutabilityContext = immutabilityContext.WithConditionalTypeParametersAsImmutable( typeSymbol ); + } + ImmutableDefinitionChecker checker = new ImmutableDefinitionChecker( compilation: ctx.Compilation, diagnosticSink: ctx.ReportDiagnostic, diff --git a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.Factory.cs b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.Factory.cs index 9c4cde1ef..56ca3fa71 100644 --- a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.Factory.cs +++ b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.Factory.cs @@ -89,7 +89,7 @@ internal partial class ImmutabilityContext { internal static ImmutabilityContext Create( Compilation compilation ) { ImmutableDictionary compilationAssmeblies = GetCompilationAssemblies( compilation ); - var builder = ImmutableArray.CreateBuilder( DefaultExtraTypes.Length ); + var builder = ImmutableDictionary.CreateBuilder(); foreach( ( string typeName, string qualifiedAssembly ) in DefaultExtraTypes ) { INamedTypeSymbol type; @@ -112,10 +112,13 @@ internal partial class ImmutabilityContext { type ); - builder.Add( info ); + builder.Add( type, info ); } - return new ImmutabilityContext( builder.ToImmutable() ); + return new ImmutabilityContext( + extraImmutableTypes: builder.ToImmutable(), + conditionalTypeParamemters: ImmutableHashSet.Empty + ); } private static ImmutableDictionary GetCompilationAssemblies( Compilation compilation ) { diff --git a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.cs b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.cs index de796579d..163f55980 100644 --- a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.cs +++ b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutabilityContext.cs @@ -17,6 +17,7 @@ namespace D2L.CodeStyle.Analyzers.Immutability { /// internal sealed partial class ImmutabilityContext { private readonly ImmutableDictionary m_extraImmutableTypes; + private readonly ImmutableHashSet m_conditionalTypeParameters; // Hard code this to avoid looking up the ITypeSymbol to include it in m_extraImmutableTypes private static readonly ImmutableHashSet m_totallyImmutableSpecialTypes = ImmutableHashSet.Create( @@ -40,12 +41,34 @@ internal sealed partial class ImmutabilityContext { SpecialType.System_ValueType ); - private ImmutabilityContext( IEnumerable extraImmutableTypes ) { - m_extraImmutableTypes = extraImmutableTypes - .ToImmutableDictionary( - info => info.Type, - info => info - ); + private ImmutabilityContext( + ImmutableDictionary extraImmutableTypes, + ImmutableHashSet conditionalTypeParamemters + ) { + m_extraImmutableTypes = extraImmutableTypes; + m_conditionalTypeParameters = conditionalTypeParamemters; + } + + /// + /// This function is intended to provide a new immutability context that considers the conditional type parameters + /// of the ConditionallyImmutable type being checked as immutable. It is important this is only used in those cases, + /// and not for instance while validating type arguments to an [Immutable] type parameter. + /// + /// The ConditionallyImmutable type definition being checked + /// + public ImmutabilityContext WithConditionalTypeParametersAsImmutable( + INamedTypeSymbol type + ) { + if( !Attributes.Objects.ConditionallyImmutable.IsDefined( type ) ) { + throw new InvalidOperationException( $"{nameof( WithConditionalTypeParametersAsImmutable )} should only be called on ConditionallyImmutable types" ); + } + + var conditionalTypeParameters = type.TypeParameters.Where( p => Attributes.Objects.OnlyIf.IsDefined( p ) ); + + return new ImmutabilityContext( + extraImmutableTypes: m_extraImmutableTypes, + conditionalTypeParamemters: m_conditionalTypeParameters.Union( conditionalTypeParameters ) + ); } /// @@ -138,6 +161,10 @@ out diagnostic return true; } + if( type is ITypeParameterSymbol tp && m_conditionalTypeParameters.Contains( tp ) ) { + return true; + } + diagnostic = Diagnostic.Create( Diagnostics.TypeParameterIsNotKnownToBeImmutable, getLocation(), @@ -218,10 +245,6 @@ ITypeSymbol type return ImmutableTypeKind.Total; } - if( Attributes.Objects.OnlyIf.IsDefined( type ) ) { - return ImmutableTypeKind.Total; - } - if ( Attributes.Objects.ImmutableBaseClass.IsDefined( type ) ) { return ImmutableTypeKind.Instance; } diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs index e945291fd..faf549163 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs @@ -769,8 +769,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static T /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly T m_field; + static /* TypeParameterIsNotKnownToBeImmutable(T) */ T /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly /* TypeParameterIsNotKnownToBeImmutable(T) */ T /**/ m_field; T /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; readonly T m_field; T Property { get; } @@ -778,8 +778,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static T /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/ = new T(); - static readonly T m_field = new T(); + static T /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/ = /* TypeParameterIsNotKnownToBeImmutable(T) */ new T() /**/; + static readonly T m_field = /* TypeParameterIsNotKnownToBeImmutable(T) */ new T() /**/; T /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/ = new T(); readonly T m_field = new T(); T Property { get; } = new T() @@ -828,8 +828,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static Types.SomeImmutableGenericInterfaceGivenT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceGivenT m_field; + static /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenT /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenT /**/ m_field; Types.SomeImmutableGenericInterfaceGivenT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; readonly Types.SomeImmutableGenericInterfaceGivenT m_field; Types.SomeImmutableGenericInterfaceGivenT Property { get; } @@ -845,8 +845,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. Types.SomeImmutableGenericInterfaceGivenT Property { get { return default; } } - static Types.SomeImmutableGenericInterfaceGivenU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceGivenU m_field; + static /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenU /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenU /**/ m_field; Types.SomeImmutableGenericInterfaceGivenU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; readonly Types.SomeImmutableGenericInterfaceGivenU m_field; Types.SomeImmutableGenericInterfaceGivenU Property { get; } @@ -863,8 +863,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static Types.SomeImmutableGenericInterfaceGivenTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceGivenTU m_field; + static /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ m_field; Types.SomeImmutableGenericInterfaceGivenTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; readonly Types.SomeImmutableGenericInterfaceGivenTU m_field; Types.SomeImmutableGenericInterfaceGivenTU Property { get; } @@ -872,8 +872,8 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static /* TypeParameterIsNotKnownToBeImmutable(U) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly /* TypeParameterIsNotKnownToBeImmutable(U) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ m_field; + static /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly /* TypeParameterIsNotKnownToBeImmutable(T) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ m_field; /* TypeParameterIsNotKnownToBeImmutable(U) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; readonly /* TypeParameterIsNotKnownToBeImmutable(U) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ m_field; /* TypeParameterIsNotKnownToBeImmutable(U) */ Types.SomeImmutableGenericInterfaceGivenTU /**/ Property { get; } @@ -889,12 +889,12 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static Types.SomeImmutableGenericInterfaceRestrictingT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceRestrictingT m_field; - Types.SomeImmutableGenericInterfaceRestrictingT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - readonly Types.SomeImmutableGenericInterfaceRestrictingT m_field; - Types.SomeImmutableGenericInterfaceRestrictingT Property { get; } - Types.SomeImmutableGenericInterfaceRestrictingT Property { get { return default; } } + static Types.SomeImmutableGenericInterfaceRestrictingT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly Types.SomeImmutableGenericInterfaceRestrictingT m_field; + Types.SomeImmutableGenericInterfaceRestrictingT /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + readonly Types.SomeImmutableGenericInterfaceRestrictingT m_field; + Types.SomeImmutableGenericInterfaceRestrictingT Property { get; } + Types.SomeImmutableGenericInterfaceRestrictingT Property { get { return default; } } @@ -906,12 +906,12 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. Types.SomeImmutableGenericInterfaceRestrictingT Property { get { return default; } } - static Types.SomeImmutableGenericInterfaceRestrictingU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceRestrictingU m_field; - Types.SomeImmutableGenericInterfaceRestrictingU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - readonly Types.SomeImmutableGenericInterfaceRestrictingU m_field; - Types.SomeImmutableGenericInterfaceRestrictingU Property { get; } - Types.SomeImmutableGenericInterfaceRestrictingU Property { get { return default; } } + static Types.SomeImmutableGenericInterfaceRestrictingU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly Types.SomeImmutableGenericInterfaceRestrictingU m_field; + Types.SomeImmutableGenericInterfaceRestrictingU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + readonly Types.SomeImmutableGenericInterfaceRestrictingU m_field; + Types.SomeImmutableGenericInterfaceRestrictingU Property { get; } + Types.SomeImmutableGenericInterfaceRestrictingU Property { get { return default; } } @@ -924,29 +924,29 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. - static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } + static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } - static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } + static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } - static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; - readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } - Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } + static Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + static readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU /* MemberIsNotReadOnly(Field, m_field, AnalyzedImmutableGenericClassGivenT) */ m_field /**/; + readonly Types.SomeImmutableGenericInterfaceRestrictingTU m_field; + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get; } + Types.SomeImmutableGenericInterfaceRestrictingTU Property { get { return default; } } void Method() { @@ -954,17 +954,17 @@ public sealed class AnalyzedImmutableGenericClassGivenT<[ConditionallyImmutable. Types.SomeGenericMethod(); Types.SomeGenericMethod(); Types.SomeGenericMethod(); - Types.SomeGenericMethodRestrictingT(); + Types.SomeGenericMethodRestrictingT(); Types.SomeGenericMethodRestrictingT(); - Types.SomeGenericMethodRestrictingT(); + Types.SomeGenericMethodRestrictingT(); Types.SomeGenericMethodRestrictingT(); Types.SomeGenericMethodRestrictingU(); - Types.SomeGenericMethodRestrictingU(); - Types.SomeGenericMethodRestrictingU(); + Types.SomeGenericMethodRestrictingU(); + Types.SomeGenericMethodRestrictingU(); Types.SomeGenericMethodRestrictingU(); - Types.SomeGenericMethodRestrictingTU(); - Types.SomeGenericMethodRestrictingTU(); - Types.SomeGenericMethodRestrictingTU(); + Types.SomeGenericMethodRestrictingTU(); + Types.SomeGenericMethodRestrictingTU(); + Types.SomeGenericMethodRestrictingTU(); Types.SomeGenericMethodRestrictingTU(); } }