diff --git a/docs/generators/adapter.md b/docs/generators/adapter.md index 7b5980a..1ac242d 100644 --- a/docs/generators/adapter.md +++ b/docs/generators/adapter.md @@ -248,6 +248,7 @@ public static partial class Adapters | **PKADP015** | Error | Mapping method must be accessible (public or internal) | | **PKADP016** | Error | Static members are not supported | | **PKADP017** | Error | Ref-return members are not supported | +| **PKADP018** | Error | Indexers are not supported | ## Limitations diff --git a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs index e2565a3..85d8f5f 100644 --- a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs +++ b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs @@ -38,6 +38,7 @@ public sealed class AdapterGenerator : IIncrementalGenerator private const string DiagIdMappingMethodNotAccessible = "PKADP015"; private const string DiagIdStaticMembersNotSupported = "PKADP016"; private const string DiagIdRefReturnNotSupported = "PKADP017"; + private const string DiagIdIndexersNotSupported = "PKADP018"; private static readonly DiagnosticDescriptor HostNotStaticPartialDescriptor = new( id: DiagIdHostNotStaticPartial, @@ -175,6 +176,14 @@ public sealed class AdapterGenerator : IIncrementalGenerator defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + private static readonly DiagnosticDescriptor IndexersNotSupportedDescriptor = new( + id: DiagIdIndexersNotSupported, + title: "Indexers are not supported", + messageFormat: "Target type '{0}' contains indexer '{1}' which is not supported by the adapter generator", + category: "PatternKit.Generators.Adapter", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + public void Initialize(IncrementalGeneratorInitializationContext context) { // Find all class declarations with [GenerateAdapter] attribute @@ -192,11 +201,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var node = typeContext.TargetNode; + // Track generated adapter type names to detect conflicts (namespace -> type name -> location) + var generatedAdapters = new Dictionary>(); + // Process each [GenerateAdapter] attribute on the host foreach (var attr in typeContext.Attributes.Where(a => a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Adapter.GenerateAdapterAttribute")) { - GenerateAdapterForAttribute(spc, hostSymbol, attr, node, typeContext.SemanticModel); + GenerateAdapterForAttribute(spc, hostSymbol, attr, node, typeContext.SemanticModel, generatedAdapters); } }); } @@ -206,7 +218,8 @@ private void GenerateAdapterForAttribute( INamedTypeSymbol hostSymbol, AttributeData attribute, SyntaxNode node, - SemanticModel semanticModel) + SemanticModel semanticModel, + Dictionary> generatedAdapters) { // Validate host is static partial if (!IsStaticPartial(node)) @@ -277,11 +290,23 @@ private void GenerateAdapterForAttribute( if (config.TargetType.TypeKind == TypeKind.Class && config.TargetType.IsAbstract) { var hasAccessibleParameterlessCtor = config.TargetType.InstanceConstructors - .Any(c => c.Parameters.Length == 0 && - (c.DeclaredAccessibility == Accessibility.Public || - c.DeclaredAccessibility == Accessibility.Protected || - c.DeclaredAccessibility == Accessibility.ProtectedOrInternal || - c.DeclaredAccessibility == Accessibility.Internal)); + .Any(c => + { + if (c.Parameters.Length > 0) + return false; + + var accessibility = c.DeclaredAccessibility; + if (accessibility == Accessibility.Public || + accessibility == Accessibility.Protected || + accessibility == Accessibility.ProtectedOrInternal) + return true; + + // Internal constructors are only accessible if in the same assembly + if (accessibility == Accessibility.Internal) + return semanticModel.Compilation.IsSymbolAccessibleWithin(c, semanticModel.Compilation.Assembly); + + return false; + }); if (!hasAccessibleParameterlessCtor) { @@ -396,7 +421,7 @@ private void GenerateAdapterForAttribute( ? string.Empty : hostSymbol.ContainingNamespace.ToDisplayString()); - // Check for type name conflict (PKADP006) + // Check for type name conflict (PKADP006) - both in existing compilation and in current generator run if (HasTypeNameConflict(semanticModel.Compilation, ns, adapterTypeName)) { context.ReportDiagnostic(Diagnostic.Create( @@ -407,6 +432,24 @@ private void GenerateAdapterForAttribute( return; } + // Check for conflict with adapters being generated in this run + var normalizedNs = string.IsNullOrEmpty(ns) ? "" : ns; + if (!generatedAdapters.ContainsKey(normalizedNs)) + generatedAdapters[normalizedNs] = new Dictionary(); + + if (generatedAdapters[normalizedNs].ContainsKey(adapterTypeName)) + { + context.ReportDiagnostic(Diagnostic.Create( + TypeNameConflictDescriptor, + node.GetLocation(), + adapterTypeName, + string.IsNullOrEmpty(ns) ? "" : ns)); + return; + } + + // Track this adapter type name + generatedAdapters[normalizedNs][adapterTypeName] = node.GetLocation(); + // Generate adapter var source = GenerateAdapterCode( adapterTypeName, @@ -499,6 +542,16 @@ private List ValidateTargetMembers(INamedTypeSymbol targetType, Loca evt.Name)); } + // Check for indexers (not supported) - must be checked before other property checks + if (member is IPropertySymbol propertySymbol && propertySymbol.IsIndexer) + { + diagnostics.Add(Diagnostic.Create( + IndexersNotSupportedDescriptor, + location, + targetType.Name, + propertySymbol.ToDisplayString())); + } + // Check for settable properties (not supported) if (member is IPropertySymbol prop && !prop.IsIndexer && prop.SetMethod is not null) { @@ -510,7 +563,7 @@ private List ValidateTargetMembers(INamedTypeSymbol targetType, Loca } // Check for ref-return properties (not supported) - if (member is IPropertySymbol refProp && refProp.ReturnsByRef) + if (member is IPropertySymbol refProp && !refProp.IsIndexer && refProp.ReturnsByRef) { diagnostics.Add(Diagnostic.Create( RefReturnNotSupportedDescriptor, diff --git a/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md b/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md index 947541b..ce3dcad 100644 --- a/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md +++ b/src/PatternKit.Generators/AnalyzerReleases.Unshipped.md @@ -115,3 +115,4 @@ PKADP014 | PatternKit.Generators.Adapter | Error | Nested or generic host not su PKADP015 | PatternKit.Generators.Adapter | Error | Mapping method must be accessible PKADP016 | PatternKit.Generators.Adapter | Error | Static members are not supported PKADP017 | PatternKit.Generators.Adapter | Error | Ref-return members are not supported +PKADP018 | PatternKit.Generators.Adapter | Error | Indexers are not supported