From 4a73a0e30a59e0edb174b534df270905fb1f4d46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:53:07 +0000 Subject: [PATCH 1/4] Initial plan From e5f2345114100a5236c73419bc8505d6ccf09782 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:57:30 +0000 Subject: [PATCH 2/4] fix(adapter): Apply PR review fixes - enum defaults, partial types, ordering, accessibility, interface DIMs Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> --- .../Adapter/AdapterGenerator.cs | 73 ++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs index 4623089..7214a96 100644 --- a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs +++ b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs @@ -210,8 +210,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var node = typeContext.TargetNode; // Process each [GenerateAdapter] attribute on the host - foreach (var attr in typeContext.Attributes.Where(a => - a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Adapter.GenerateAdapterAttribute")) + var generateAdapterAttributes = typeContext.Attributes + .Where(a => a.AttributeClass?.ToDisplayString() == "PatternKit.Generators.Adapter.GenerateAdapterAttribute"); + + foreach (var attr in generateAdapterAttributes) { GenerateAdapterForAttribute(spc, hostSymbol, attr, node, typeContext.SemanticModel, generatedAdapters); } @@ -311,6 +313,10 @@ private void GenerateAdapterForAttribute( if (accessibility == Accessibility.Internal) return semanticModel.Compilation.IsSymbolAccessibleWithin(c, semanticModel.Compilation.Assembly); + // Private protected constructors are accessible to derived types in the same assembly + if (accessibility == Accessibility.ProtectedAndInternal) + return semanticModel.Compilation.IsSymbolAccessibleWithin(c, semanticModel.Compilation.Assembly); + return false; }); @@ -500,7 +506,27 @@ private static bool IsValidAdapteeType(INamedTypeSymbol type) private static bool HasTypeNameConflict(Compilation compilation, string ns, string typeName) { var fullName = string.IsNullOrEmpty(ns) ? typeName : $"{ns}.{typeName}"; - return compilation.GetTypeByMetadataName(fullName) is not null; + var existingType = compilation.GetTypeByMetadataName(fullName); + if (existingType is null) + return false; + + // If the type comes from metadata only, we can't add a partial declaration safely. + if (existingType.DeclaringSyntaxReferences.Length == 0) + return true; + + // Only treat this as a conflict if any declaration is non-partial. + foreach (var syntaxRef in existingType.DeclaringSyntaxReferences) + { + var syntax = syntaxRef.GetSyntax(); + if (syntax is not TypeDeclarationSyntax typeDecl) + return true; + + if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) + return true; + } + + // All declarations are partial; allow the generator to emit its own partial type. + return false; } private List ValidateTargetMembers(INamedTypeSymbol targetType, Location fallbackLocation) @@ -729,7 +755,19 @@ private static List GetTargetMembers(INamedTypeSymbol targetType) var membersToProcess = type.GetMembers() .Where(m => !m.IsStatic) // Exclude static members - .Where(m => !isAbstractClass || m.IsAbstract); + .Where(m => + { + // For abstract classes, only include abstract members + if (isAbstractClass) + return m.IsAbstract; + + // For interfaces, only include abstract members (exclude default implementations) + // Properties/methods with implementations (C# 8.0+) are not abstract + if (type.TypeKind == TypeKind.Interface) + return m.IsAbstract; + + return true; + }); foreach (var member in membersToProcess) { @@ -768,18 +806,18 @@ private static List GetTargetMembers(INamedTypeSymbol targetType) // This provides both readable (contract-ordered) output and deterministic ordering return members.OrderBy(m => { - // Try to get syntax declaration order (file path + line number) + // Try to get syntax declaration order by line number within the containing type var syntaxRef = m.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxRef != null) { var location = syntaxRef.GetSyntax().GetLocation(); var lineSpan = location.GetLineSpan(); - // Return a tuple of (file path, line number) for natural ordering - return (lineSpan.Path, lineSpan.StartLinePosition.Line, 0); + // Use only line number for ordering, not file path (which varies across machines) + // Within a single type declaration, line order is sufficient and deterministic + return lineSpan.StartLinePosition.Line; } // For metadata-only symbols without source, use a fallback ordering - // Use the symbol's metadata token which is stable across compilations - return (string.Empty, int.MaxValue, m.MetadataToken); + return int.MaxValue; }) .ThenBy(m => m.Kind) .ThenBy(m => m.Name) @@ -1067,6 +1105,23 @@ private static string GetDefaultValue(IParameterSymbol param) return " = default"; } + // Handle enum parameters specially to emit proper enum syntax + if (param.Type.TypeKind == TypeKind.Enum && param.Type is INamedTypeSymbol enumType) + { + // Try to find the enum field matching this value + var enumField = enumType.GetMembers() + .OfType() + .FirstOrDefault(f => f.HasConstantValue && Equals(f.ConstantValue, value)); + + if (enumField != null) + { + return $" = {enumType.ToDisplayString(FullyQualifiedFormat)}.{enumField.Name}"; + } + + // Fallback: cast the numeric value + return $" = ({enumType.ToDisplayString(FullyQualifiedFormat)}){value}"; + } + var literal = SymbolDisplay.FormatPrimitive(value, quoteStrings: true, useHexadecimalNumbers: false); return " = " + literal; } From 8a3eb3a3e478065db265ce27275477e36698371f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:00:45 +0000 Subject: [PATCH 3/4] refactor(adapter): Combine internal accessibility checks and clarify ordering comment Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> --- .../Adapter/AdapterGenerator.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs index 7214a96..60a77e6 100644 --- a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs +++ b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs @@ -309,12 +309,9 @@ private void GenerateAdapterForAttribute( 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); - - // Private protected constructors are accessible to derived types in the same assembly - if (accessibility == Accessibility.ProtectedAndInternal) + // Internal and private protected constructors are only accessible within the same assembly + if (accessibility == Accessibility.Internal || + accessibility == Accessibility.ProtectedAndInternal) return semanticModel.Compilation.IsSymbolAccessibleWithin(c, semanticModel.Compilation.Assembly); return false; @@ -806,14 +803,15 @@ private static List GetTargetMembers(INamedTypeSymbol targetType) // This provides both readable (contract-ordered) output and deterministic ordering return members.OrderBy(m => { - // Try to get syntax declaration order by line number within the containing type + // Try to get syntax declaration order by line number var syntaxRef = m.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxRef != null) { var location = syntaxRef.GetSyntax().GetLocation(); var lineSpan = location.GetLineSpan(); // Use only line number for ordering, not file path (which varies across machines) - // Within a single type declaration, line order is sufficient and deterministic + // Note: For types split across multiple partial files, this may not preserve + // perfect declaration order, but ThenBy clauses provide stable fallback ordering return lineSpan.StartLinePosition.Line; } // For metadata-only symbols without source, use a fallback ordering From 452536d2bbb1540dc13cbd9a66b1067c918fe860 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:04:54 +0000 Subject: [PATCH 4/4] refactor(adapter): Simplify conflict detection and filtering logic with LINQ Co-authored-by: JerrettDavis <2610199+JerrettDavis@users.noreply.github.com> --- .../Adapter/AdapterGenerator.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs index 60a77e6..ebab8f9 100644 --- a/src/PatternKit.Generators/Adapter/AdapterGenerator.cs +++ b/src/PatternKit.Generators/Adapter/AdapterGenerator.cs @@ -512,18 +512,9 @@ private static bool HasTypeNameConflict(Compilation compilation, string ns, stri return true; // Only treat this as a conflict if any declaration is non-partial. - foreach (var syntaxRef in existingType.DeclaringSyntaxReferences) - { - var syntax = syntaxRef.GetSyntax(); - if (syntax is not TypeDeclarationSyntax typeDecl) - return true; - - if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) - return true; - } - - // All declarations are partial; allow the generator to emit its own partial type. - return false; + return existingType.DeclaringSyntaxReferences.Any(syntaxRef => + syntaxRef.GetSyntax() is not TypeDeclarationSyntax typeDecl || + !typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)); } private List ValidateTargetMembers(INamedTypeSymbol targetType, Location fallbackLocation) @@ -754,13 +745,9 @@ private static List GetTargetMembers(INamedTypeSymbol targetType) .Where(m => !m.IsStatic) // Exclude static members .Where(m => { - // For abstract classes, only include abstract members - if (isAbstractClass) - return m.IsAbstract; - - // For interfaces, only include abstract members (exclude default implementations) - // Properties/methods with implementations (C# 8.0+) are not abstract - if (type.TypeKind == TypeKind.Interface) + // For abstract classes and interfaces, only include abstract members + // (interfaces: exclude default implementations added in C# 8.0+) + if (isAbstractClass || type.TypeKind == TypeKind.Interface) return m.IsAbstract; return true;