Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
71 changes: 62 additions & 9 deletions src/PatternKit.Generators/Adapter/AdapterGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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<string, Dictionary<string, Location>>();

// 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);
}
});
}
Expand All @@ -206,7 +218,8 @@ private void GenerateAdapterForAttribute(
INamedTypeSymbol hostSymbol,
AttributeData attribute,
SyntaxNode node,
SemanticModel semanticModel)
SemanticModel semanticModel,
Dictionary<string, Dictionary<string, Location>> generatedAdapters)
{
// Validate host is static partial
if (!IsStaticPartial(node))
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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(
Expand All @@ -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<string, Location>();

if (generatedAdapters[normalizedNs].ContainsKey(adapterTypeName))
{
context.ReportDiagnostic(Diagnostic.Create(
TypeNameConflictDescriptor,
node.GetLocation(),
adapterTypeName,
string.IsNullOrEmpty(ns) ? "<global>" : ns));
return;
}

// Track this adapter type name
generatedAdapters[normalizedNs][adapterTypeName] = node.GetLocation();

// Generate adapter
var source = GenerateAdapterCode(
adapterTypeName,
Expand Down Expand Up @@ -499,6 +542,16 @@ private List<Diagnostic> 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)
{
Expand All @@ -510,7 +563,7 @@ private List<Diagnostic> 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,
Expand Down
1 change: 1 addition & 0 deletions src/PatternKit.Generators/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -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