Skip to content

Commit bb51007

Browse files
fix(adapter): Address enum defaults, partial types, ordering, and DIM filtering (#116)
* Initial plan * fix(adapter): Apply PR review fixes - enum defaults, partial types, ordering, accessibility, interface DIMs Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> * refactor(adapter): Combine internal accessibility checks and clarify ordering comment Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> * refactor(adapter): Simplify conflict detection and filtering logic with LINQ Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com>
1 parent 21f3d1c commit bb51007

1 file changed

Lines changed: 51 additions & 11 deletions

File tree

src/PatternKit.Generators/Adapter/AdapterGenerator.cs

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
210210
var node = typeContext.TargetNode;
211211

212212
// Process each [GenerateAdapter] attribute on the host
213-
foreach (var attr in typeContext.Attributes.Where(a =>
214-
a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Adapter.GenerateAdapterAttribute"))
213+
var generateAdapterAttributes = typeContext.Attributes
214+
.Where(a => a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Adapter.GenerateAdapterAttribute");
215+
216+
foreach (var attr in generateAdapterAttributes)
215217
{
216218
GenerateAdapterForAttribute(spc, hostSymbol, attr, node, typeContext.SemanticModel, generatedAdapters);
217219
}
@@ -307,8 +309,9 @@ private void GenerateAdapterForAttribute(
307309
accessibility == Accessibility.ProtectedOrInternal)
308310
return true;
309311

310-
// Internal constructors are only accessible if in the same assembly
311-
if (accessibility == Accessibility.Internal)
312+
// Internal and private protected constructors are only accessible within the same assembly
313+
if (accessibility == Accessibility.Internal ||
314+
accessibility == Accessibility.ProtectedAndInternal)
312315
return semanticModel.Compilation.IsSymbolAccessibleWithin(c, semanticModel.Compilation.Assembly);
313316

314317
return false;
@@ -500,7 +503,18 @@ private static bool IsValidAdapteeType(INamedTypeSymbol type)
500503
private static bool HasTypeNameConflict(Compilation compilation, string ns, string typeName)
501504
{
502505
var fullName = string.IsNullOrEmpty(ns) ? typeName : $"{ns}.{typeName}";
503-
return compilation.GetTypeByMetadataName(fullName) is not null;
506+
var existingType = compilation.GetTypeByMetadataName(fullName);
507+
if (existingType is null)
508+
return false;
509+
510+
// If the type comes from metadata only, we can't add a partial declaration safely.
511+
if (existingType.DeclaringSyntaxReferences.Length == 0)
512+
return true;
513+
514+
// Only treat this as a conflict if any declaration is non-partial.
515+
return existingType.DeclaringSyntaxReferences.Any(syntaxRef =>
516+
syntaxRef.GetSyntax() is not TypeDeclarationSyntax typeDecl ||
517+
!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword));
504518
}
505519

506520
private List<Diagnostic> ValidateTargetMembers(INamedTypeSymbol targetType, Location fallbackLocation)
@@ -729,7 +743,15 @@ private static List<ISymbol> GetTargetMembers(INamedTypeSymbol targetType)
729743

730744
var membersToProcess = type.GetMembers()
731745
.Where(m => !m.IsStatic) // Exclude static members
732-
.Where(m => !isAbstractClass || m.IsAbstract);
746+
.Where(m =>
747+
{
748+
// For abstract classes and interfaces, only include abstract members
749+
// (interfaces: exclude default implementations added in C# 8.0+)
750+
if (isAbstractClass || type.TypeKind == TypeKind.Interface)
751+
return m.IsAbstract;
752+
753+
return true;
754+
});
733755

734756
foreach (var member in membersToProcess)
735757
{
@@ -768,18 +790,19 @@ private static List<ISymbol> GetTargetMembers(INamedTypeSymbol targetType)
768790
// This provides both readable (contract-ordered) output and deterministic ordering
769791
return members.OrderBy(m =>
770792
{
771-
// Try to get syntax declaration order (file path + line number)
793+
// Try to get syntax declaration order by line number
772794
var syntaxRef = m.DeclaringSyntaxReferences.FirstOrDefault();
773795
if (syntaxRef != null)
774796
{
775797
var location = syntaxRef.GetSyntax().GetLocation();
776798
var lineSpan = location.GetLineSpan();
777-
// Return a tuple of (file path, line number) for natural ordering
778-
return (lineSpan.Path, lineSpan.StartLinePosition.Line, 0);
799+
// Use only line number for ordering, not file path (which varies across machines)
800+
// Note: For types split across multiple partial files, this may not preserve
801+
// perfect declaration order, but ThenBy clauses provide stable fallback ordering
802+
return lineSpan.StartLinePosition.Line;
779803
}
780804
// For metadata-only symbols without source, use a fallback ordering
781-
// Use the symbol's metadata token which is stable across compilations
782-
return (string.Empty, int.MaxValue, m.MetadataToken);
805+
return int.MaxValue;
783806
})
784807
.ThenBy(m => m.Kind)
785808
.ThenBy(m => m.Name)
@@ -1067,6 +1090,23 @@ private static string GetDefaultValue(IParameterSymbol param)
10671090
return " = default";
10681091
}
10691092

1093+
// Handle enum parameters specially to emit proper enum syntax
1094+
if (param.Type.TypeKind == TypeKind.Enum && param.Type is INamedTypeSymbol enumType)
1095+
{
1096+
// Try to find the enum field matching this value
1097+
var enumField = enumType.GetMembers()
1098+
.OfType<IFieldSymbol>()
1099+
.FirstOrDefault(f => f.HasConstantValue && Equals(f.ConstantValue, value));
1100+
1101+
if (enumField != null)
1102+
{
1103+
return $" = {enumType.ToDisplayString(FullyQualifiedFormat)}.{enumField.Name}";
1104+
}
1105+
1106+
// Fallback: cast the numeric value
1107+
return $" = ({enumType.ToDisplayString(FullyQualifiedFormat)}){value}";
1108+
}
1109+
10701110
var literal = SymbolDisplay.FormatPrimitive(value, quoteStrings: true, useHexadecimalNumbers: false);
10711111
return " = " + literal;
10721112
}

0 commit comments

Comments
 (0)