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
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -224,7 +223,7 @@ static bool IncludeInternalSymbols(ISymbolFilter filter) =>
yield return constructor;
}

// Synthesize a base class initializer.
// Synthesize a base class initializer.
public static ConstructorInitializerSyntax GenerateBaseConstructorInitializer(this IMethodSymbol baseTypeConstructor)
{
return SyntaxFactory.ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, baseTypeConstructor.CreateDefaultArgumentList());
Expand All @@ -248,33 +247,5 @@ public static ArgumentListSyntax CreateDefaultArgumentList(this IMethodSymbol me

return argumentList;
}

// Locates constructor generated by the compiler for `record Foo(...)` syntax
// If the type is a record and the compiler generated constructor is found it will be returned, otherwise null.
// The compiler will not generate a constructor in the case where the user defined it themself without using an argument list
// in the record declaration, or if the record has no parameters.
public static bool TryGetRecordConstructor(this INamedTypeSymbol type, [NotNullWhen(true)] out IMethodSymbol? recordConstructor)
{
if (!type.IsRecord)
{
recordConstructor = null;
return false;
}

// Locate the compiler generated Deconstruct method.
var deconstructMethod = (IMethodSymbol?)type.GetMembers("Deconstruct")
.FirstOrDefault(m => m is IMethodSymbol && m.IsCompilerGenerated());

// Locate the compiler generated constructor by matching parameters to Deconstruct - since we cannot locate it with an attribute.
recordConstructor = (IMethodSymbol?)type.GetMembers(".ctor")
.FirstOrDefault(m => m is IMethodSymbol method &&
method.MethodKind == MethodKind.Constructor &&
(deconstructMethod == null ?
method.Parameters.IsEmpty :
method.Parameters.Select(p => p.Type).SequenceEqual(
deconstructMethod.Parameters.Select(p => p.Type), SymbolEqualityComparer.Default)));

return recordConstructor != null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
<TargetFrameworks>$(NetToolMinimum);$(NetFrameworkToolCurrent)</TargetFrameworks>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Compile Include="$(RepoRoot)src\Common\NullableAttributes.cs" LinkBase="Common" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<ProjectReference Include="..\..\Microsoft.DotNet.ApiSymbolExtensions\Microsoft.DotNet.ApiSymbolExtensions.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.DotNet.ApiSymbolExtensions;
using Microsoft.DotNet.ApiSymbolExtensions.Filtering;

namespace Microsoft.DotNet.GenAPI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Reference Include="System.IO.Compression" />
<Compile Include="$(RepoRoot)src\Common\NullableAttributes.cs" LinkBase="Common" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace Microsoft.DotNet.ApiSymbolExtensions
Expand Down Expand Up @@ -126,5 +127,37 @@ public static bool IsEventAdderOrRemover(this IMethodSymbol method) =>
method.MethodKind == MethodKind.EventRemove ||
method.Name.StartsWith("add_", StringComparison.Ordinal) ||
method.Name.StartsWith("remove_", StringComparison.Ordinal);

/// <summary>
/// Attempts to locate and return the constructor generated by the compiler for `record Foo(...)` syntax.
/// The compiler will not generate a constructor in the case where the user defined it themself without using an argument list
/// in the record declaration, or if the record has no parameters.
/// </summary>
/// <param name="type">The type to check for a compiler generated constructor.</param>
/// <param name="recordConstructor">When this method returns <see langword="true"/>, then the compiler generated constructor for the record is stored in this out parameter, otherwise it becomes <see langword="null" />.</param>
/// <returns><see langword="true" /> if the type is a record and the compiler generated constructor is found, otherwise <see langword="false"/>.</returns>
public static bool TryGetRecordConstructor(this INamedTypeSymbol type, [NotNullWhen(true)] out IMethodSymbol? recordConstructor)
{
if (!type.IsRecord)
{
recordConstructor = null;
return false;
}

// Locate the compiler generated Deconstruct method.
var deconstructMethod = (IMethodSymbol?)type.GetMembers("Deconstruct")
.FirstOrDefault(m => m is IMethodSymbol && m.IsCompilerGenerated());

// Locate the compiler generated constructor by matching parameters to Deconstruct - since we cannot locate it with an attribute.
recordConstructor = (IMethodSymbol?)type.GetMembers(".ctor")
.FirstOrDefault(m => m is IMethodSymbol method &&
method.MethodKind == MethodKind.Constructor &&
(deconstructMethod == null ?
method.Parameters.IsEmpty :
method.Parameters.Select(p => p.Type).SequenceEqual(
deconstructMethod.Parameters.Select(p => p.Type), SymbolEqualityComparer.Default)));

return recordConstructor != null;
}
}
}