diff --git a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs index 1c4053e6..30fbe3d1 100644 --- a/src/D2L.CodeStyle.Analyzers/Diagnostics.cs +++ b/src/D2L.CodeStyle.Analyzers/Diagnostics.cs @@ -760,7 +760,7 @@ public static class Diagnostics { public static readonly DiagnosticDescriptor UnappliedConditionalImmutability = new DiagnosticDescriptor( id: "D2L0103", title: "A type parameter marked [ConditionallyImmutable.OnlyIf] was not applied to a [ConditionallyImmutable] implemented type", - messageFormat: "{0} should apply all [ConditionallyImmutable.OnlyIf] type parameters to the [ConditionallyImmutable] {1} {2}", + messageFormat: "The conditional type parameter <{0}> was not applied to the [ConditionallyImmutable] {1} {2}", category: "Correctness", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true diff --git a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableAttributeConsistencyChecker.cs b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableAttributeConsistencyChecker.cs index b7d92bda..9642c461 100644 --- a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableAttributeConsistencyChecker.cs +++ b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableAttributeConsistencyChecker.cs @@ -139,39 +139,45 @@ void RaiseMissingAttribute() { } void InspectConditionalParameterApplication() { - foreach( ITypeParameterSymbol typeParameter in typeInfo.ConditionalTypeParameters ) { + TypeDeclarationSyntax implementingSyntax = null; + + foreach( var p in typeInfo.GetConditionalTypeParameters() ) { bool parameterUsed = false; - for( int i = 0; i < baseTypeInfo.Type.TypeArguments.Length && !parameterUsed; i++ ) { - ITypeSymbol typeArgument = baseTypeInfo.Type.TypeArguments[ i ]; - if( !SymbolEqualityComparer.Default.Equals( typeParameter, typeArgument ) ) { + foreach( var bp in baseTypeInfo.GetConditionalTypeParameters() ) { + ITypeSymbol typeArgument = baseTypeInfo.Type.TypeArguments[ bp.OriginalOrdinal ]; + + if( !SymbolEqualityComparer.Default.Equals( p.TypeParameter, typeArgument ) ) { continue; } - ITypeParameterSymbol baseTypeParameter = baseTypeInfo.Type.TypeParameters[ i ]; - if( baseTypeInfo.ConditionalTypeParameters.Contains( baseTypeParameter, SymbolEqualityComparer.Default ) ) { - parameterUsed = true; - break; - } + parameterUsed = true; + break; + }; + + if( parameterUsed ) { + continue; } - if( !parameterUsed ) { - (TypeDeclarationSyntax syntax, _) = typeInfo.Type.ExpensiveGetSyntaxImplementingType( + if( implementingSyntax is null ) { + (implementingSyntax, _) = typeInfo.Type.ExpensiveGetSyntaxImplementingType( baseTypeOrInterface: baseTypeInfo.Type, compilation: m_compilation, cancellationToken ); - m_diagnosticSink( - Diagnostic.Create( - Diagnostics.UnappliedConditionalImmutability, - syntax.Identifier.GetLocation(), - typeInfo.Type.GetFullTypeName(), - baseTypeInfo.Type.TypeKind == TypeKind.Interface ? "interface" : "base class", - baseTypeInfo.Type.GetFullTypeName() - ) - ); - return; } - } + + TypeParameterSyntax parameterSyntax = implementingSyntax.TypeParameterList!.Parameters[ p.OriginalOrdinal ]; + + m_diagnosticSink( + Diagnostic.Create( + Diagnostics.UnappliedConditionalImmutability, + parameterSyntax.Identifier.GetLocation(), + p.TypeParameter.Name, + baseTypeInfo.Type.TypeKind == TypeKind.Interface ? "interface" : "base class", + baseTypeInfo.Type.GetFullTypeName() + ) + ); + }; } } diff --git a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableTypeInfo.cs b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableTypeInfo.cs index 82865ae6..a47687b7 100644 --- a/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableTypeInfo.cs +++ b/src/D2L.CodeStyle.Analyzers/Immutability/ImmutableTypeInfo.cs @@ -33,8 +33,19 @@ ImmutableArray conditionalTypeParameters public bool IsConditional { get; } - public IEnumerable ConditionalTypeParameters => Type.TypeParameters.Where( IsConditionalParameter ); - private bool IsConditionalParameter( ITypeParameterSymbol _, int index ) => m_conditionalTypeParameters[ index ]; + public IEnumerable<(ITypeParameterSymbol TypeParameter, int OriginalOrdinal)> GetConditionalTypeParameters() { + if( !IsConditional ) { + yield break; + } + + for( int i = 0; i < Type.TypeParameters.Length; i++ ) { + if( !m_conditionalTypeParameters[ i ] ) { + continue; + } + + yield return (Type.TypeParameters[ i ], i); + } + } public bool IsImmutableDefinition( ImmutabilityContext context, diff --git a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs index 84f12fd0..fbecd89a 100644 --- a/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs +++ b/tests/D2L.CodeStyle.Analyzers.Test/Specs/ImmutabilityAnalyzer.cs @@ -1427,7 +1427,7 @@ public sealed class ImmutableClassImplementingConditionallyImmutable<[Immutable] public sealed class ConditionallyImmutableClassImplementingConditionallyImmutable<[ConditionallyImmutable.OnlyIf] T> : ISomethingConditionallyImmutable { } [ConditionallyImmutable] - public sealed class /* UnappliedConditionalImmutability(ConsistencyTests.ConditionallyImmutableImplementerWithUnappliedCondition, interface, ConsistencyTests.ISomethingConditionallyImmutable) */ ConditionallyImmutableImplementerWithUnappliedCondition /**/<[ConditionallyImmutable.OnlyIf] T, [ConditionallyImmutable.OnlyIf] U> : ISomethingConditionallyImmutable { } + public sealed class ConditionallyImmutableImplementerWithUnappliedCondition<[ConditionallyImmutable.OnlyIf] T, [ConditionallyImmutable.OnlyIf] /* UnappliedConditionalImmutability(U, interface, ConsistencyTests.ISomethingConditionallyImmutable) */ U /**/> : ISomethingConditionallyImmutable { } [ConditionallyImmutable] public sealed class /* MissingTransitiveImmutableAttribute(ConsistencyTests.ConditionallyImmutableImplementingImmutable, , interface, ConsistencyTests.ISomethingImmutable) */ ConditionallyImmutableImplementingImmutable /**/<[ConditionallyImmutable.OnlyIf] T> : ISomethingImmutable { }