From dbf1525986bc55a9c26e24d05b8e1a4559776fb8 Mon Sep 17 00:00:00 2001 From: Victor Irzak Date: Sun, 13 Sep 2026 20:15:06 -0400 Subject: [PATCH] Name the core API for 1.0 Ahead of the stable release, which fixes these names: - MethodParent and MethodParentDeclaration become ContainingTypeKind and ContainingType, with Kind, Name and TypeParameters, matching the 'containing type' the documentation uses. MethodLocation.Parents becomes ContainingTypes. - MethodLocation.IsCSharp14Extension becomes IsInExtensionBlock, and Index becomes OverloadIndex. - ClonedMethod.MethodName becomes FileName, which is what it holds, and DisableNullable becomes NullableDisabled. - ReportedDiagnostic.Trivia becomes MessageArguments, which takes any number of them. - CloningRewriter.ProcessSymbol and ProcessType become Qualify, and MakeType becomes FullyQualifiedName. QualifiedName would have hidden SyntaxFactory.QualifiedName in every rewriter which imports SyntaxFactory statically. Nothing generated changes. Generated with Claude Code --- .../VariantGenerator.cs | 6 +- .../VariantRewriter.cs | 4 +- src/Zomp.MethodCloning/ClonedMethod.cs | 26 ++++---- src/Zomp.MethodCloning/ClonedMethodOutput.cs | 4 +- src/Zomp.MethodCloning/ClonedMethodSource.cs | 18 +++--- src/Zomp.MethodCloning/CloningRewriter.cs | 62 +++++++++---------- src/Zomp.MethodCloning/ContainingType.cs | 42 +++++++++++++ ...{MethodParent.cs => ContainingTypeKind.cs} | 5 +- src/Zomp.MethodCloning/MethodLocation.cs | 32 +++++----- .../MethodParentDeclaration.cs | 41 ------------ src/Zomp.MethodCloning/MethodSignature.cs | 2 +- src/Zomp.MethodCloning/README.md | 2 +- src/Zomp.MethodCloning/ReportedDiagnostic.cs | 31 +++++----- .../SampleGenerator/SuffixGenerator.cs | 2 +- .../IdentityCloneGenerator.cs | 4 +- 15 files changed, 143 insertions(+), 138 deletions(-) create mode 100644 src/Zomp.MethodCloning/ContainingType.cs rename src/Zomp.MethodCloning/{MethodParent.cs => ContainingTypeKind.cs} (69%) delete mode 100644 src/Zomp.MethodCloning/MethodParentDeclaration.cs diff --git a/src/Zomp.MethodCloning.Variants/VariantGenerator.cs b/src/Zomp.MethodCloning.Variants/VariantGenerator.cs index 5d47b33..42c555a 100644 --- a/src/Zomp.MethodCloning.Variants/VariantGenerator.cs +++ b/src/Zomp.MethodCloning.Variants/VariantGenerator.cs @@ -96,14 +96,14 @@ private static ImmutableArray Clone(CloneTarget target, Cancellati // Each variant keeps the nullable context the method was written in, so that a method // written without annotations does not gain them in its variants. - var disableNullable = !context.SemanticModel.GetNullableContext(syntax.SpanStart).AnnotationsEnabled(); + var nullableDisabled = !context.SemanticModel.GetNullableContext(syntax.SpanStart).AnnotationsEnabled(); var name = syntax.Identifier.ValueText; if (name.IndexOf(original, StringComparison.Ordinal) < 0) { var diagnostic = ReportedDiagnostic.Create(VariantDiagnostics.NameWithoutOriginal, syntax.Identifier.GetLocation(), name); - return ImmutableArray.Create(ClonedMethod.Create(location, syntax, syntax, disableNullable, ImmutableArray.Create(diagnostic))); + return ImmutableArray.Create(ClonedMethod.Create(location, syntax, syntax, nullableDisabled, ImmutableArray.Create(diagnostic))); } var clones = ImmutableArray.CreateBuilder(); @@ -128,7 +128,7 @@ private static ImmutableArray Clone(CloneTarget target, Cancellati // Each file is named after the method and the variant. The variant's name alone would // not do: methods written for different variants, such as GetBmpMetadata and an // overload GetGifMetadata, can both produce a GetPngMetadata. - clones.Add(ClonedMethod.Create(variantLocation, syntax, rewritten, disableNullable, rewriter.Diagnostics, $"{name}.{to}")); + clones.Add(ClonedMethod.Create(variantLocation, syntax, rewritten, nullableDisabled, rewriter.Diagnostics, $"{name}.{to}")); } return clones.ToImmutable(); diff --git a/src/Zomp.MethodCloning.Variants/VariantRewriter.cs b/src/Zomp.MethodCloning.Variants/VariantRewriter.cs index 11ccb41..bfa83b9 100644 --- a/src/Zomp.MethodCloning.Variants/VariantRewriter.cs +++ b/src/Zomp.MethodCloning.Variants/VariantRewriter.cs @@ -44,7 +44,7 @@ internal sealed class VariantRewriter(SemanticModel semanticModel, MethodDeclara // The base qualifies a return type written as a generic, nullable or qualified name, but // leaves one written as a simple name, which may name the variant, as it is. var returnType = node.ReturnType is IdentifierNameSyntax - ? ProcessType(node.ReturnType).WithTriviaFrom(clone.ReturnType) + ? Qualify(node.ReturnType).WithTriviaFrom(clone.ReturnType) : clone.ReturnType; return clone @@ -105,7 +105,7 @@ internal sealed class VariantRewriter(SemanticModel semanticModel, MethodDeclara if (SemanticModel.Compilation.GetTypeByMetadataName(counterpartName) is { } counterpart && !SymbolEqualityComparer.Default.Equals(counterpart, type)) { - return ProcessSymbol(counterpart); + return Qualify(counterpart); } if (methodLocation is not null && !kept.Contains(type, SymbolEqualityComparer.Default)) diff --git a/src/Zomp.MethodCloning/ClonedMethod.cs b/src/Zomp.MethodCloning/ClonedMethod.cs index dbafd88..4f85f8f 100644 --- a/src/Zomp.MethodCloning/ClonedMethod.cs +++ b/src/Zomp.MethodCloning/ClonedMethod.cs @@ -13,17 +13,17 @@ namespace Zomp.MethodCloning; /// A method rewritten by a transformation, ready to be emitted into a file of its own. /// /// Where the original method sits. -/// Name of the original method. +/// Stem of the name of the file the method is emitted to, by default the name of the original method. /// The rewritten method. -/// True to leave the #nullable enable directive out of the file. +/// True to leave the #nullable enable directive out of the file. /// Diagnostics the transformation reported. /// True if there are errors in , in which case nothing is emitted. /// Signature the rewritten method will be emitted with, used to detect colliding overloads. internal sealed record ClonedMethod( MethodLocation Location, - string MethodName, + string FileName, string Implementation, - bool DisableNullable, + bool NullableDisabled, EquatableArray Diagnostics, bool HasErrors, MethodSignature? Signature) @@ -34,21 +34,21 @@ internal sealed record ClonedMethod( /// Where the original method sits. /// Declaration of the original method. /// Result of rewriting the method, or the extension block which holds it. - /// True to leave the #nullable enable directive out of the file. + /// True to leave the #nullable enable directive out of the file. /// Diagnostics the transformation reported. - /// - /// Name the file is emitted under. A transformation which clones a method more than once - /// gives each copy its own, since files named after the same method would collide. Defaults to - /// the name of the original method. + /// + /// Stem of the name of the file the method is emitted to. A transformation which clones a + /// method more than once gives each copy its own, since files named after the same method + /// would collide. Defaults to the name of the original method. /// /// A new . public static ClonedMethod Create( MethodLocation location, MethodDeclarationSyntax original, SyntaxNode rewritten, - bool disableNullable, + bool nullableDisabled, ImmutableArray diagnostics, - string? name = null) + string? fileName = null) { var hasErrors = false; foreach (var diagnostic in diagnostics) @@ -58,9 +58,9 @@ public static ClonedMethod Create( return new( location, - name ?? original.Identifier.ValueText, + fileName ?? original.Identifier.ValueText, rewritten.ToFullString(), - disableNullable, + nullableDisabled, diagnostics, hasErrors, MethodSignature.Create(rewritten, location, original)); diff --git a/src/Zomp.MethodCloning/ClonedMethodOutput.cs b/src/Zomp.MethodCloning/ClonedMethodOutput.cs index 6ab764d..4e45d1b 100644 --- a/src/Zomp.MethodCloning/ClonedMethodOutput.cs +++ b/src/Zomp.MethodCloning/ClonedMethodOutput.cs @@ -74,9 +74,9 @@ private static (ClonedMethod Method, string Path, string Content) GenerateSource { var location = m.Location; - var scope = location.Scope + (location.IsCSharp14Extension ? ".ext" : string.Empty); + var scope = location.Scope + (location.IsInExtensionBlock ? ".ext" : string.Empty); - var method = m.MethodName + (location.Index == 1 ? string.Empty : "_" + location.Index); + var method = m.FileName + (location.OverloadIndex == 1 ? string.Empty : "_" + location.OverloadIndex); var sourcePath = BuildFileName(scope, method); diff --git a/src/Zomp.MethodCloning/ClonedMethodSource.cs b/src/Zomp.MethodCloning/ClonedMethodSource.cs index 50a84fb..d17dc99 100644 --- a/src/Zomp.MethodCloning/ClonedMethodSource.cs +++ b/src/Zomp.MethodCloning/ClonedMethodSource.cs @@ -81,17 +81,17 @@ public static string Generate(ClonedMethod method) } // Handle classes - foreach (var parent in location.Parents) + foreach (var parent in location.ContainingTypes) { var indent = new string(' ', 4 * i); var modifiers = string.Join(string.Empty, parent.Modifiers.Select(z => GetKeyword((SyntaxKind)z) + " ")); - var parentType = parent.MethodParent switch + var parentType = parent.Kind switch { - MethodParent.Class => "class", - MethodParent.Interface => "interface", - MethodParent.Struct => "struct", - MethodParent.Record => "record" + ContainingTypeKind.Class => "class", + ContainingTypeKind.Interface => "interface", + ContainingTypeKind.Struct => "struct", + ContainingTypeKind.Record => "record" + parent.ClassOrStructKeyword.Kind() switch { SyntaxKind.StructKeyword => " struct", @@ -101,8 +101,8 @@ public static string Generate(ClonedMethod method) _ => throw new NotImplementedException("Cannot handle the parent of the method"), }; - var classDeclarationLine = $"{modifiers}partial {parentType} {parent.ParentName}{(parent.TypeParameterListSyntax.IsEmpty ? string.Empty - : "<" + string.Join(", ", parent.TypeParameterListSyntax) + ">")}"; + var classDeclarationLine = $"{modifiers}partial {parentType} {parent.Name}{(parent.TypeParameters.IsEmpty ? string.Empty + : "<" + string.Join(", ", parent.TypeParameters) + ">")}"; _ = sbBegin.Append(FormattableString.Invariant($$""" {{indent}}{{classDeclarationLine}} @@ -136,7 +136,7 @@ public static string Generate(ClonedMethod method) } var beforeNamespace = $""" -// {(method.DisableNullable ? string.Empty : """ +// {(method.NullableDisabled ? string.Empty : """ #nullable enable """)}{sbOuterUsings} diff --git a/src/Zomp.MethodCloning/CloningRewriter.cs b/src/Zomp.MethodCloning/CloningRewriter.cs index 724b68f..7bb71cd 100644 --- a/src/Zomp.MethodCloning/CloningRewriter.cs +++ b/src/Zomp.MethodCloning/CloningRewriter.cs @@ -50,7 +50,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla // Handles nameof(Type) return GetSymbol(node.Expression) is ITypeSymbol typeSymbol && !TypeAlreadyQualified(typeSymbol) - ? @base.WithExpression(ProcessSymbol(typeSymbol)).WithTriviaFrom(@base) + ? @base.WithExpression(Qualify(typeSymbol)).WithTriviaFrom(@base) : @base; } @@ -60,7 +60,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla var @base = (ArrayTypeSyntax)base.VisitArrayType(node)!; var elementType = TypeAlreadyQualified(node.ElementType) ? @base.ElementType - : ProcessType(@base.ElementType); + : Qualify(@base.ElementType); return @base.WithElementType(elementType).WithTriviaFrom(@base); } @@ -74,7 +74,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla return @base; } - var retval = @base.WithName(ProcessSymbol(ms.ContainingType)); + var retval = @base.WithName(Qualify(ms.ContainingType)); return retval; } @@ -87,12 +87,12 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla { if (GetSymbol(node.Left) is IFieldSymbol leftSymbol) { - @base = @base.WithLeft(ProcessSymbol(leftSymbol).WithTriviaFrom(node.Left)); + @base = @base.WithLeft(Qualify(leftSymbol).WithTriviaFrom(node.Left)); } if (GetSymbol(node.Right) is ISymbol symbol) { - @base = @base.WithRight(ProcessSymbol(symbol).WithTriviaFrom(node.Right)); + @base = @base.WithRight(Qualify(symbol).WithTriviaFrom(node.Right)); } return @base.WithTriviaFrom(@base); @@ -105,14 +105,14 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla public override SyntaxNode? VisitCastExpression(CastExpressionSyntax node) { var @base = (CastExpressionSyntax)base.VisitCastExpression(node)!; - return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// public override SyntaxNode? VisitCatchDeclaration(CatchDeclarationSyntax node) { var @base = (CatchDeclarationSyntax)base.VisitCatchDeclaration(node)!; - return @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// @@ -123,7 +123,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla { LiteralExpressionSyntax or MemberAccessExpressionSyntax => @base, _ => SemanticModel.GetTypeInfo(node.Expression).Type is { } type - ? @base.WithExpression(ProcessSymbol(type).WithTriviaFrom(@base)) + ? @base.WithExpression(Qualify(type).WithTriviaFrom(@base)) : @base, }; } @@ -132,21 +132,21 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla public override SyntaxNode? VisitDeclarationExpression(DeclarationExpressionSyntax node) { var @base = (DeclarationExpressionSyntax)base.VisitDeclarationExpression(node)!; - return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// public override SyntaxNode? VisitDeclarationPattern(DeclarationPatternSyntax node) { var @base = (DeclarationPatternSyntax)base.VisitDeclarationPattern(node)!; - return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// public override SyntaxNode? VisitForEachStatement(ForEachStatementSyntax node) { var @base = (ForEachStatementSyntax)base.VisitForEachStatement(node)!; - return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// @@ -189,7 +189,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla if (node.Parent is TypeArgumentListSyntax) { - return ProcessType(node); + return Qualify(node); } return @base; @@ -221,7 +221,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla // Rewrite static invocation (eg. File.ReadAllText) if (GetSymbol(node.Expression) is ITypeSymbol && node.Expression is TypeSyntax type) { - var newType = ProcessType(type); + var newType = Qualify(type); if (newType != type) { @base = @base.WithExpression(newType); @@ -234,7 +234,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla else if (node.Expression is MemberAccessExpressionSyntax && GetSymbol(node.Expression) is INamedTypeSymbol { IsGenericType: false } namedType) { - @base = @base.WithExpression(ProcessSymbol(namedType).WithTriviaFrom(@base.Expression)); + @base = @base.WithExpression(Qualify(namedType).WithTriviaFrom(@base.Expression)); } return @base; @@ -249,7 +249,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla { var @base = (NullableTypeSyntax)base.VisitNullableType(node)!; - return TypeAlreadyQualified(node.ElementType) ? @base : @base.WithElementType(ProcessType(@base.ElementType)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.ElementType) ? @base : @base.WithElementType(Qualify(@base.ElementType)).WithTriviaFrom(@base); } /// @@ -263,7 +263,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla return @base; } - var newType = ProcessSymbol(t); + var newType = Qualify(t); return newType == node.Type ? @base : @base.WithType(newType); } @@ -273,7 +273,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla var @base = (ParameterSyntax)base.VisitParameter(node)!; return node.Type is null || TypeAlreadyQualified(node.Type) ? @base - : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// @@ -283,7 +283,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla // The generic name on the right is fully qualified by its own visitor, so it replaces the // whole name, and takes over the indentation which led the left side. - return @base.Right is GenericNameSyntax ? @base.Right.WithTriviaFrom(@base) : (SyntaxNode)ProcessType(node); + return @base.Right is GenericNameSyntax ? @base.Right.WithTriviaFrom(@base) : (SyntaxNode)Qualify(node); } /// @@ -294,7 +294,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla var newTuples = new List(); foreach (var t in node.Elements.Zip(@base.Elements, (original, visited) => (original, visited))) { - var newType = TypeAlreadyQualified(t.original.Type) ? t.visited.Type : ProcessType(t.original.Type); + var newType = TypeAlreadyQualified(t.original.Type) ? t.visited.Type : Qualify(t.original.Type); newTuples.Add(TupleElement(newType, t.original.Identifier)); } @@ -305,7 +305,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla public override SyntaxNode? VisitTypeConstraint(TypeConstraintSyntax node) { var @base = (TypeConstraintSyntax)base.VisitTypeConstraint(node)!; - var newType = ProcessType(@base.Type); + var newType = Qualify(@base.Type); return newType == @base.Type ? @base : @base.WithType(newType).WithTriviaFrom(@base); } @@ -313,7 +313,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla public override SyntaxNode? VisitTypeOfExpression(TypeOfExpressionSyntax node) { var @base = (TypeOfExpressionSyntax)base.VisitTypeOfExpression(node)!; - return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(ProcessType(node.Type)).WithTriviaFrom(@base); + return TypeAlreadyQualified(node.Type) ? @base : @base.WithType(Qualify(node.Type)).WithTriviaFrom(@base); } /// @@ -330,7 +330,7 @@ internal abstract class CloningRewriter(SemanticModel semanticModel, MethodDecla && newTypeString == typeString)) { // not replaced - newType = ProcessType(type); + newType = Qualify(type); if (newType == type) { @@ -394,7 +394,7 @@ protected static bool TypeAlreadyQualified(TypeSyntax type) /// /// The type, or any other symbol, which is named as it is. /// The name. - protected string MakeType(ISymbol symbol) + protected string FullyQualifiedName(ISymbol symbol) => symbol switch { INamedTypeSymbol nts => MapTypeName(nts) ?? symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), @@ -406,11 +406,11 @@ protected string MakeType(ISymbol symbol) /// /// The symbol. /// The name. - protected SimpleNameSyntax ProcessSymbol(ISymbol typeSymbol) => MapSymbol(typeSymbol) ?? typeSymbol switch + protected SimpleNameSyntax Qualify(ISymbol typeSymbol) => MapSymbol(typeSymbol) ?? typeSymbol switch { - INamedTypeSymbol nts => IdentifierName(MakeType(nts)), - IArrayTypeSymbol ats => IdentifierName(MakeType(ats.ElementType) + $"[{new string(',', ats.Rank - 1)}]"), - IFieldSymbol fs => IdentifierName(MakeType(fs.Type) + '.' + fs.Name), + INamedTypeSymbol nts => IdentifierName(FullyQualifiedName(nts)), + IArrayTypeSymbol ats => IdentifierName(FullyQualifiedName(ats.ElementType) + $"[{new string(',', ats.Rank - 1)}]"), + IFieldSymbol fs => IdentifierName(FullyQualifiedName(fs.Type) + '.' + fs.Name), _ => IdentifierName(typeSymbol.Name), }; @@ -419,10 +419,10 @@ protected string MakeType(ISymbol symbol) /// /// The type. /// The qualified type, or the type itself if it needs nothing. - protected TypeSyntax ProcessType(TypeSyntax typeSyntax) => typeSyntax switch + protected TypeSyntax Qualify(TypeSyntax typeSyntax) => typeSyntax switch { IdentifierNameSyntax { Identifier.ValueText: "var" } => typeSyntax, - IdentifierNameSyntax or QualifiedNameSyntax => ProcessSyntaxUsingSymbol(typeSyntax), + IdentifierNameSyntax or QualifiedNameSyntax => QualifyUsingSymbol(typeSyntax), _ => typeSyntax, }; @@ -453,9 +453,9 @@ private static bool TypeAlreadyQualified(ITypeSymbol type) => type is INamedTypeSymbol namedType && namedType is { IsGenericType: true }; - private TypeSyntax ProcessSyntaxUsingSymbol(TypeSyntax typeSyntax) + private TypeSyntax QualifyUsingSymbol(TypeSyntax typeSyntax) { var typeSymbol = SemanticModel.GetTypeInfo(typeSyntax).Type; - return typeSymbol is null ? typeSyntax : ProcessSymbol(typeSymbol).WithTriviaFrom(typeSyntax); + return typeSymbol is null ? typeSyntax : Qualify(typeSymbol).WithTriviaFrom(typeSyntax); } } diff --git a/src/Zomp.MethodCloning/ContainingType.cs b/src/Zomp.MethodCloning/ContainingType.cs new file mode 100644 index 0000000..c723bef --- /dev/null +++ b/src/Zomp.MethodCloning/ContainingType.cs @@ -0,0 +1,42 @@ +// +// Zomp.MethodCloning, compiled into the generator which references the package. Marked as +// generated so that the analyzers of that generator leave it alone. +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; +using System.Linq; + +namespace Zomp.MethodCloning; + +/// +/// A type a method is nested in, which the copy of the method declares again, as partial, +/// around itself. +/// +/// Kind of the type. +/// Name of the type, without its type parameters. +/// Modifiers of the type other than partial, as raw syntax kinds. +/// Names of the type parameters of the type. +/// For a record, the class or struct keyword, when one is written. +internal sealed record ContainingType(ContainingTypeKind Kind, string Name, EquatableArray Modifiers, EquatableArray TypeParameters, SyntaxToken ClassOrStructKeyword) +{ + public ContainingType(ContainingTypeKind kind, SyntaxToken name, SyntaxTokenList modifiers, TypeParameterListSyntax? typeParameterList, SyntaxToken classOrStructKeyword = default) + : this( + kind, + name.ValueText, + modifiers.Select(z => (ushort)z.RawKind).Where(z => z != (ushort)SyntaxKind.PartialKeyword).ToImmutableArray(), + (typeParameterList is null ? [] : typeParameterList.Parameters.Select(z => z.Identifier.ValueText)).ToImmutableArray(), + classOrStructKeyword) + { + } + + /// + /// Gets the name of the type with its type parameters, so that Class, Class{T} + /// and Class{T,T2} stay distinct. + /// + public string ScopeName => TypeParameters.IsEmpty + ? Name + : Name + "{" + string.Join(",", TypeParameters) + "}"; +} diff --git a/src/Zomp.MethodCloning/MethodParent.cs b/src/Zomp.MethodCloning/ContainingTypeKind.cs similarity index 69% rename from src/Zomp.MethodCloning/MethodParent.cs rename to src/Zomp.MethodCloning/ContainingTypeKind.cs index d2a49ed..07b2561 100644 --- a/src/Zomp.MethodCloning/MethodParent.cs +++ b/src/Zomp.MethodCloning/ContainingTypeKind.cs @@ -5,7 +5,10 @@ namespace Zomp.MethodCloning; -internal enum MethodParent +/// +/// The kind of a type a method is nested in. +/// +internal enum ContainingTypeKind { Class, Struct, diff --git a/src/Zomp.MethodCloning/MethodLocation.cs b/src/Zomp.MethodCloning/MethodLocation.cs index a230517..82a56ce 100644 --- a/src/Zomp.MethodCloning/MethodLocation.cs +++ b/src/Zomp.MethodCloning/MethodLocation.cs @@ -17,26 +17,26 @@ namespace Zomp.MethodCloning; /// Everything around a method which a copy of it needs in order to compile in a file of its own: /// the namespaces and types it is nested in, and the using directives in scope where it was written. /// -/// Position of the method among the members of its type which share its name, starting at 1. +/// Position of the method among the members of its type which share its name, starting at 1. /// Namespaces the method is under, outermost first. /// Using directives the source file declares outside its namespace. /// Using directives the source file declares inside its namespace. /// True if the namespace is file scoped. -/// True if the method is a member of a C# 14 extension block. -/// Types the method is nested in, outermost first. +/// True if the method is a member of a C# 14 extension block. +/// Types the method is nested in, outermost first. internal sealed record MethodLocation( - int Index, + int OverloadIndex, EquatableArray Namespaces, EquatableArray OuterUsings, EquatableArray InnerUsings, bool IsNamespaceFileScoped, - bool IsCSharp14Extension, - EquatableArray Parents) + bool IsInExtensionBlock, + EquatableArray ContainingTypes) { /// /// Gets the namespaces and types the method is nested in, joined with dots. /// - public string Scope => string.Join(".", Namespaces.Concat(Parents.Select(static p => p.ScopeName))); + public string Scope => string.Join(".", Namespaces.Concat(ContainingTypes.Select(static p => p.ScopeName))); /// /// Locates a method. @@ -55,7 +55,7 @@ public static bool TryCreate( location = null; root = null; - var parents = ImmutableArray.CreateBuilder(); + var parents = ImmutableArray.CreateBuilder(); SyntaxNode? node = method; #if ROSLYN_5_0_OR_GREATER ExtensionBlockDeclarationSyntax? extensionParent = null; @@ -71,12 +71,12 @@ public static bool TryCreate( } #endif - MethodParentDeclaration? mpd = node switch + ContainingType? mpd = node switch { - ClassDeclarationSyntax o => new(MethodParent.Class, o.Identifier, o.Modifiers, o.TypeParameterList), - StructDeclarationSyntax o => new(MethodParent.Struct, o.Identifier, o.Modifiers, o.TypeParameterList), - RecordDeclarationSyntax o => new(MethodParent.Record, o.Identifier, o.Modifiers, o.TypeParameterList, o.ClassOrStructKeyword), - InterfaceDeclarationSyntax o => new(MethodParent.Interface, o.Identifier, o.Modifiers, o.TypeParameterList), + ClassDeclarationSyntax o => new(ContainingTypeKind.Class, o.Identifier, o.Modifiers, o.TypeParameterList), + StructDeclarationSyntax o => new(ContainingTypeKind.Struct, o.Identifier, o.Modifiers, o.TypeParameterList), + RecordDeclarationSyntax o => new(ContainingTypeKind.Record, o.Identifier, o.Modifiers, o.TypeParameterList, o.ClassOrStructKeyword), + InterfaceDeclarationSyntax o => new(ContainingTypeKind.Interface, o.Identifier, o.Modifiers, o.TypeParameterList), _ => null, }; @@ -131,10 +131,10 @@ public static bool TryCreate( } #if ROSLYN_5_0_OR_GREATER - var isCSharp14Extension = extensionParent is not null; + var isInExtensionBlock = extensionParent is not null; root = extensionParent ?? (SyntaxNode)method; #else - var isCSharp14Extension = false; + var isInExtensionBlock = false; root = method; #endif @@ -144,7 +144,7 @@ public static bool TryCreate( outerUsings.ToImmutable(), innerUsings.ToImmutable(), isNamespaceFileScoped, - isCSharp14Extension, + isInExtensionBlock, parents.ToImmutable()); return true; diff --git a/src/Zomp.MethodCloning/MethodParentDeclaration.cs b/src/Zomp.MethodCloning/MethodParentDeclaration.cs deleted file mode 100644 index d8b5bfc..0000000 --- a/src/Zomp.MethodCloning/MethodParentDeclaration.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Zomp.MethodCloning, compiled into the generator which references the package. Marked as -// generated so that the analyzers of that generator leave it alone. -#nullable enable - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Immutable; -using System.Linq; - -namespace Zomp.MethodCloning; - -/// -/// Represents a type a is nested in. -/// -/// Type of container. -/// Class name. -/// A list of modifiers. -/// A list of type parameters. -/// Indicates whether struct or class are explicitly specified for a record. -internal sealed record MethodParentDeclaration(MethodParent MethodParent, string ParentName, EquatableArray Modifiers, EquatableArray TypeParameterListSyntax, SyntaxToken ClassOrStructKeyword) -{ - public MethodParentDeclaration(MethodParent methodParent, SyntaxToken parentName, SyntaxTokenList modifiers, TypeParameterListSyntax? typeParameterList, SyntaxToken classOrStructKeyword = default) - : this( - methodParent, - parentName.ValueText, - modifiers.Select(z => (ushort)z.RawKind).Where(z => z != (ushort)SyntaxKind.PartialKeyword).ToImmutableArray(), - (typeParameterList is null ? [] : typeParameterList.Parameters.Select(z => z.Identifier.ValueText)).ToImmutableArray(), - classOrStructKeyword) - { - } - - /// - /// Gets the name of the type with its type parameters, so that Class, Class{T} - /// and Class{T,T2} stay distinct. - /// - public string ScopeName => TypeParameterListSyntax.IsEmpty - ? ParentName - : ParentName + "{" + string.Join(",", TypeParameterListSyntax) + "}"; -} diff --git a/src/Zomp.MethodCloning/MethodSignature.cs b/src/Zomp.MethodCloning/MethodSignature.cs index 3b8fd14..c5e0410 100644 --- a/src/Zomp.MethodCloning/MethodSignature.cs +++ b/src/Zomp.MethodCloning/MethodSignature.cs @@ -97,5 +97,5 @@ public static EquatableArray FindCollisions(ImmutableArray keys /// Descriptor of the diagnostic. /// A new . public ReportedDiagnostic ToDiagnostic(DiagnosticDescriptor descriptor) - => new(descriptor, FilePath, TextSpan, LineSpan, Key); + => new(descriptor, FilePath, TextSpan, LineSpan, ImmutableArray.Create(Key)); } diff --git a/src/Zomp.MethodCloning/README.md b/src/Zomp.MethodCloning/README.md index 45d9f3a..0df0eb2 100644 --- a/src/Zomp.MethodCloning/README.md +++ b/src/Zomp.MethodCloning/README.md @@ -41,7 +41,7 @@ public sealed class CopyGenerator : IIncrementalGenerator } var copy = new RenameRewriter(target.Context.SemanticModel, target.Syntax).Visit(root); - return ClonedMethod.Create(location, target.Syntax, copy, disableNullable: false, ImmutableArray.Empty); + return ClonedMethod.Create(location, target.Syntax, copy, nullableDisabled: false, ImmutableArray.Empty); } } diff --git a/src/Zomp.MethodCloning/ReportedDiagnostic.cs b/src/Zomp.MethodCloning/ReportedDiagnostic.cs index 1c2276b..15d7118 100644 --- a/src/Zomp.MethodCloning/ReportedDiagnostic.cs +++ b/src/Zomp.MethodCloning/ReportedDiagnostic.cs @@ -5,19 +5,22 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; +using System.Collections.Immutable; namespace Zomp.MethodCloning; /// -/// Basic diagnostic description for reporting diagnostic inside the incremental pipeline. +/// A diagnostic in a form the incremental pipeline can compare and cache. A +/// holds a , which keeps the syntax tree alive and +/// compares by reference. /// -/// Diagnostic descriptor. -/// File path. -/// Text span. -/// Line span. -/// Trivia. +/// Descriptor of the diagnostic. +/// Path of the file the diagnostic is reported in. +/// Span the diagnostic is reported at. +/// Lines the diagnostic is reported at. +/// Arguments of the descriptor's message format. /// -internal sealed record ReportedDiagnostic(DiagnosticDescriptor Descriptor, string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan, string Trivia) +internal sealed record ReportedDiagnostic(DiagnosticDescriptor Descriptor, string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan, EquatableArray MessageArguments) { /// /// Implicitly converts to . @@ -27,17 +30,15 @@ public static implicit operator Diagnostic(ReportedDiagnostic diagnostic) => Diagnostic.Create( descriptor: diagnostic.Descriptor, location: Location.Create(diagnostic.FilePath, diagnostic.TextSpan, diagnostic.LineSpan), - messageArgs: [diagnostic.Trivia]); + messageArgs: [.. diagnostic.MessageArguments]); /// /// Creates a new from and . /// - /// Descriptor. - /// Location. - /// Trivia. + /// Descriptor of the diagnostic. + /// Where the diagnostic is reported. + /// Arguments of the descriptor's message format. /// A new . - public static ReportedDiagnostic Create(DiagnosticDescriptor descriptor, Location location, string trivia) - { - return new(descriptor, location.SourceTree?.FilePath ?? string.Empty, location.SourceSpan, location.GetLineSpan().Span, trivia); - } + public static ReportedDiagnostic Create(DiagnosticDescriptor descriptor, Location location, params string[] messageArguments) + => new(descriptor, location.SourceTree?.FilePath ?? string.Empty, location.SourceSpan, location.GetLineSpan().Span, ImmutableArray.Create(messageArguments)); } diff --git a/tests/Consumers/SampleGenerator/SuffixGenerator.cs b/tests/Consumers/SampleGenerator/SuffixGenerator.cs index c6758e5..a146479 100644 --- a/tests/Consumers/SampleGenerator/SuffixGenerator.cs +++ b/tests/Consumers/SampleGenerator/SuffixGenerator.cs @@ -35,6 +35,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var copy = new SuffixRewriter(target.Context.SemanticModel, target.Syntax).Visit(root); - return ClonedMethod.Create(location, target.Syntax, copy, disableNullable: false, ImmutableArray.Empty); + return ClonedMethod.Create(location, target.Syntax, copy, nullableDisabled: false, ImmutableArray.Empty); } } diff --git a/tests/Zomp.MethodCloning.Testing/IdentityCloneGenerator.cs b/tests/Zomp.MethodCloning.Testing/IdentityCloneGenerator.cs index 2cdd860..a12f65c 100644 --- a/tests/Zomp.MethodCloning.Testing/IdentityCloneGenerator.cs +++ b/tests/Zomp.MethodCloning.Testing/IdentityCloneGenerator.cs @@ -55,9 +55,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return null; } - var disableNullable = !context.SemanticModel.GetNullableContext(target.Syntax.SpanStart).AnnotationsEnabled(); + var nullableDisabled = !context.SemanticModel.GetNullableContext(target.Syntax.SpanStart).AnnotationsEnabled(); var clone = new IdentityRewriter(context.SemanticModel, target.Syntax).Visit(root); - return ClonedMethod.Create(location, target.Syntax, clone, disableNullable, []); + return ClonedMethod.Create(location, target.Syntax, clone, nullableDisabled, []); } }