Skip to content

Commit

Permalink
Merge pull request #42 from Sergio0694/dev/multi-targeting-fixes
Browse files Browse the repository at this point in the history
Generate [TypeForwardsTo] when needed
  • Loading branch information
Sergio0694 authored Jan 15, 2023
2 parents 5b312d6 + 6e5626a commit db53c4a
Show file tree
Hide file tree
Showing 18 changed files with 310 additions and 72 deletions.
17 changes: 11 additions & 6 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,32 @@ jobs:
uses: actions/checkout@v3
- name: Build
run: dotnet build -c ${{matrix.configuration}} /bl
shell: cmd
- name: Upload MSBuild binary log
uses: actions/upload-artifact@v3
with:
name: msbuild_log_${{matrix.configuration}}
path: msbuild.binlog
if-no-files-found: error

# Build the .msbuildproj projects to generate all the NuGet packages
build-packages:
# Run unit tests
run-tests:
if: success()
needs: [build-solution]
runs-on: windows-2022
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Run unit tests
run: dotnet test -c Release

# Build the .msbuildproj projects to generate all the NuGet packages
build-packages:
runs-on: windows-2022
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Build PolySharp package
run: dotnet build src\PolySharp.Package\PolySharp.Package.msbuildproj -c Release
shell: cmd
- name: Upload package artifacts
uses: actions/upload-artifact@v3
with:
Expand All @@ -57,12 +64,10 @@ jobs:
uses: actions/checkout@v3
- name: Create local NuGet feed
run: mkdir artifacts
shell: cmd
- name: Download package artifacts
uses: actions/download-artifact@v3
with:
name: nuget_packages
path: artifacts
- name: Build PolySharp.NuGet
run: dotnet build tests\PolySharp.NuGet\PolySharp.NuGet.csproj -c Release
shell: cmd
9 changes: 8 additions & 1 deletion PolySharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D78D3FFF-DB8
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolySharp.MinimumCSharpVersion.Tests", "tests\PolySharp.MinimumCSharpVersion.Tests\PolySharp.MinimumCSharpVersion.Tests.csproj", "{B9459D6D-247C-435D-9642-D6A933ECEECC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests", "tests\PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests\PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests.csproj", "{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests", "tests\PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests\PolySharp.PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute.Tests.csproj", "{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolySharp.TypeForwards.Tests", "tests\PolySharp.TypeForwards.Tests\PolySharp.TypeForwards.Tests.csproj", "{AAF3B574-66F1-4EF0-936A-82390E30D539}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -59,6 +61,10 @@ Global
{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB}.Release|Any CPU.Build.0 = Release|Any CPU
{AAF3B574-66F1-4EF0-936A-82390E30D539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAF3B574-66F1-4EF0-936A-82390E30D539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAF3B574-66F1-4EF0-936A-82390E30D539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAF3B574-66F1-4EF0-936A-82390E30D539}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -68,6 +74,7 @@ Global
{D78D3FFF-DB82-41B3-951F-40C7ED8F8F07} = {C0C25293-DF18-48E5-BCE9-499CB6D66BB6}
{B9459D6D-247C-435D-9642-D6A933ECEECC} = {D65D0307-1D0F-499D-945B-E33E71F251A4}
{C865CEDE-2DC9-4E1E-BB58-529E6E2A9DBB} = {D65D0307-1D0F-499D-945B-E33E71F251A4}
{AAF3B574-66F1-4EF0-936A-82390E30D539} = {D65D0307-1D0F-499D-945B-E33E71F251A4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F946EBAA-2D9B-4599-B4A2-7ECD81E0DF61}
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ It also includes the following optional runtime-supported polyfills:
The following properties are available:
- "PolySharpUsePublicAccessibilityForGeneratedTypes": makes all generated types public.
- "PolySharpIncludeRuntimeSupportedAttributes": enables polyfills for (dummy) runtime-supported attributes too.
- "PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute": uses a type alias for `[UnmanagedCallersOnly]`.
- "PolySharpUseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute": moves `[UnmanagedCallersOnly]`.
- "PolySharpExcludeGeneratedTypes": excludes specific types from generation (';' or ',' separated type names).
- "PolySharpIncludeGeneratedTypes": only includes specific types for generation (';' or ',' separated type names).
- "PolySharpExcludeTypeForwardedToDeclarations": never generates any `[TypeForwardedTo]` declarations.
3 changes: 2 additions & 1 deletion src/PolySharp.Package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ It also includes the following optional runtime-supported polyfills:
The following properties are available:
- "PolySharpUsePublicAccessibilityForGeneratedTypes": makes all generated types public.
- "PolySharpIncludeRuntimeSupportedAttributes": enables polyfills for (dummy) runtime-supported attributes too.
- "PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute": uses a type alias for `[UnmanagedCallersOnly]`.
- "PolySharpUseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute": moves `[UnmanagedCallersOnly]`.
- "PolySharpExcludeGeneratedTypes": excludes specific types from generation (';' or ',' separated type names).
- "PolySharpIncludeGeneratedTypes": only includes specific types for generation (';' or ',' separated type names).
- "PolySharpExcludeTypeForwardedToDeclarations": never generates any `[TypeForwardedTo]` declarations.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ internal static class PolySharpMSBuildProperties
public const string IncludeRuntimeSupportedAttributes = "PolySharpIncludeRuntimeSupportedAttributes";

/// <summary>
/// The MSBuild property for <see cref="Models.GenerationOptions.UseTypeAliasForUnmanagedCallersOnlyAttribute"/>.
/// The MSBuild property for <see cref="Models.GenerationOptions.UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute"/>.
/// </summary>
public const string UseTypeAliasForUnmanagedCallersOnlyAttribute = "PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute";
public const string UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute = "PolySharpUseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute";

/// <summary>
/// The MSBuild property for <see cref="Models.GenerationOptions.ExcludeGeneratedTypes"/>.
Expand All @@ -29,4 +29,9 @@ internal static class PolySharpMSBuildProperties
/// The MSBuild property for <see cref="Models.GenerationOptions.IncludeGeneratedTypes"/>.
/// </summary>
public const string IncludeGeneratedTypes = "PolySharpIncludeGeneratedTypes";

/// <summary>
/// The MSBuild property for <see cref="Models.GenerationOptions.ExcludeTypeForwardedToDeclarations"/>.
/// </summary>
public const string ExcludeTypeForwardedToDeclarations = "PolySharpExcludeTypeForwardedToDeclarations";
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ private static ImmutableArray<DiagnosticInfo> GetOptionsDiagnostics(AnalyzerConf

token.ThrowIfCancellationRequested();

// And for "UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute" as well
if (!options.IsValidMSBuildProperty(PolySharpMSBuildProperties.UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute, out string? useInteropServices2NamespaceForUnmanagedCallersOnlyAttribute))
{
builder.Add(InvalidBoolMSBuildProperty, useInteropServices2NamespaceForUnmanagedCallersOnlyAttribute, PolySharpMSBuildProperties.UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute);
}

token.ThrowIfCancellationRequested();

// And for "ExcludeTypeForwardedToDeclarations" as well
if (!options.IsValidMSBuildProperty(PolySharpMSBuildProperties.ExcludeTypeForwardedToDeclarations, out string? excludeTypeForwardedToDeclarations))
{
builder.Add(InvalidBoolMSBuildProperty, excludeTypeForwardedToDeclarations, PolySharpMSBuildProperties.ExcludeTypeForwardedToDeclarations);
}

token.ThrowIfCancellationRequested();

ImmutableArray<string> excludeGeneratedTypes = options.GetStringArrayMSBuildProperty(PolySharpMSBuildProperties.ExcludeGeneratedTypes);

// Validate the fully qualified type names for "ExcludeGeneratedTypes"
Expand Down
35 changes: 6 additions & 29 deletions src/PolySharp.SourceGenerators/Extensions/CompilationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,41 +47,18 @@ public static bool HasLanguageVersionAtLeastEqualTo(this Compilation compilation
/// <returns>Whether a type with the specified metadata name can be accessed from the given compilation.</returns>
public static bool HasAccessibleTypeWithMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
{
// Try to get the unique type with this name
INamedTypeSymbol? type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);

// If there is only a single matching symbol, check its accessibility
if (type is not null)
{
return type.CanBeAccessedFrom(compilation.Assembly);
}

// Otherwise, try to get the unique type with this name originally defined in 'compilation'
type ??= compilation.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName);

if (type is not null)
if (compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) is INamedTypeSymbol typeSymbol)
{
return type.CanBeAccessedFrom(compilation.Assembly);
return typeSymbol.CanBeAccessedFrom(compilation.Assembly);
}

// Otherwise, check whether the type is defined and accessible from any of the referenced assemblies
foreach (IModuleSymbol module in compilation.Assembly.Modules)
// Otherwise, check all available types
foreach (INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName))
{
foreach (IAssemblySymbol referencedAssembly in module.ReferencedAssemblySymbols)
if (currentTypeSymbol.CanBeAccessedFrom(compilation.Assembly))
{
if (referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName) is not INamedTypeSymbol currentType)
{
continue;
}

switch (currentType.GetEffectiveAccessibility())
{
case Accessibility.Public:
case Accessibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
return true;
default:
continue;
}
return true;
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/PolySharp.SourceGenerators/Models/GenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ namespace PolySharp.SourceGenerators.Models;
/// </summary>
/// <param name="UsePublicAccessibilityForGeneratedTypes">Whether to use public accessibility for the generated types.</param>
/// <param name="IncludeRuntimeSupportedAttributes">Whether to also generated dummy runtime supported attributes.</param>
/// <param name="UseTypeAliasForUnmanagedCallersOnlyAttribute">Whether to move the <c>[UnmanagedCallersOnly]</c> type to another namespace and add a type alias for it.</param>
/// <param name="UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute">Whether to move the <c>[UnmanagedCallersOnly]</c> type to a dummy <c>InteropServices2</c> namespace.</param>
/// <param name="ExcludeTypeForwardedToDeclarations">Whether to never generate any <c>[TypeForwardedTo]</c> declarations automatically.</param>
/// <param name="ExcludeGeneratedTypes">The collection of fully qualified type names of types to exclude from generation.</param>
/// <param name="IncludeGeneratedTypes">The collection of fully qualified type names of types to include in the generation.</param>
internal sealed record GenerationOptions(
bool UsePublicAccessibilityForGeneratedTypes,
bool IncludeRuntimeSupportedAttributes,
bool UseTypeAliasForUnmanagedCallersOnlyAttribute,
bool UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute,
bool ExcludeTypeForwardedToDeclarations,
EquatableArray<string> ExcludeGeneratedTypes,
EquatableArray<string> IncludeGeneratedTypes);
4 changes: 2 additions & 2 deletions src/PolySharp.SourceGenerators/Models/SyntaxFixupType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ internal enum SyntaxFixupType
RemoveMethodImplAttributes = 1 << 0,

/// <summary>
/// Generates the <c>[UnmanagedCallersOnly]</c> type in a different namespace and adds a <c>global using</c> type alias for it with the same name.
/// Generates the <c>[UnmanagedCallersOnly]</c> type in the <c>InteropServices2</c> dummy namespace.
/// </summary>
/// <remarks>This is needed when methods annotated with the attribute have to be assigned to delegates, which Roslyn will otherwise block.</remarks>
AliasUnmanagedCallersOnlyAttributeType = 1 << 1
UseInteropServices2ForUnmanagedCallersOnlyAttribute = 1 << 1
}
3 changes: 2 additions & 1 deletion src/PolySharp.SourceGenerators/PolySharp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@
<ItemGroup>
<CompilerVisibleProperty Include="PolySharpUsePublicAccessibilityForGeneratedTypes"/>
<CompilerVisibleProperty Include="PolySharpIncludeRuntimeSupportedAttributes"/>
<CompilerVisibleProperty Include="PolySharpUseTypeAliasForUnmanagedCallersOnlyAttribute"/>
<CompilerVisibleProperty Include="PolySharpUseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute"/>
<CompilerVisibleProperty Include="PolySharpExcludeGeneratedTypes"/>
<CompilerVisibleProperty Include="PolySharpIncludeGeneratedTypes"/>
<CompilerVisibleProperty Include="PolySharpExcludeTypeForwardedToDeclarations"/>
</ItemGroup>

<!-- Adds necessary fixups for multiline properties (replaces ';' characters with ',' and strip new lines) -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ private static GenerationOptions GetGenerationOptions(AnalyzerConfigOptionsProvi

// Do the same as above for all other available boolean properties
bool includeRuntimeSupportedAttributes = options.GetBoolMSBuildProperty(PolySharpMSBuildProperties.IncludeRuntimeSupportedAttributes);
bool useTypeAliasForUnmanagedCallersOnlyAttribute = options.GetBoolMSBuildProperty(PolySharpMSBuildProperties.UseTypeAliasForUnmanagedCallersOnlyAttribute);
bool useInteropServices2NamespaceForUnmanagedCallersOnlyAttribute = options.GetBoolMSBuildProperty(PolySharpMSBuildProperties.UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute);
bool excludeTypeForwardedToDeclarations = options.GetBoolMSBuildProperty(PolySharpMSBuildProperties.ExcludeTypeForwardedToDeclarations);

// Gather the list of any polyfills to exclude from generation (this can help to avoid conflicts with other generators). That's because
// generators see the same compilation and can't know what others will generate, so $(PolySharpExcludeGeneratedTypes) can solve this issue.
Expand All @@ -89,7 +90,8 @@ private static GenerationOptions GetGenerationOptions(AnalyzerConfigOptionsProvi
return new(
usePublicAccessibilityForGeneratedTypes,
includeRuntimeSupportedAttributes,
useTypeAliasForUnmanagedCallersOnlyAttribute,
useInteropServices2NamespaceForUnmanagedCallersOnlyAttribute,
excludeTypeForwardedToDeclarations,
excludeGeneratedTypes,
includeGeneratedTypes);
}
Expand Down Expand Up @@ -158,23 +160,40 @@ static SyntaxFixupType GetSyntaxFixupType(Compilation compilation, string name)
}

/// <summary>
/// Checks whether a given <see cref="AvailableType"/> is selected for generation
/// Checks whether a type with a specific fully qualified name is selected for generation.
/// </summary>
/// <param name="info">The input info for the current generation.</param>
/// <returns>Whether the current <see cref="AvailableType"/> is selected for generation</returns>
private static bool IsAvailableTypeSelected((AvailableType AvailableType, GenerationOptions Options) info)
/// <returns>Whether the current type is selected for generation</returns>
private static bool IsAvailableTypeSelected((string FullyQualifiedTypeName, GenerationOptions Options) info)
{
bool isExplicitlyIncluded = info.Options.IncludeGeneratedTypes.AsImmutableArray().Contains(info.FullyQualifiedTypeName);
bool isExplicitlyExcluded = info.Options.ExcludeGeneratedTypes.AsImmutableArray().Contains(info.FullyQualifiedTypeName);

// If the explicit list of types to generate isn't empty, take it into account.
// Types will be generated only if explicitly requested and not explicitly excluded.
if (info.Options.IncludeGeneratedTypes.Length > 0)
{
return
info.Options.IncludeGeneratedTypes.AsImmutableArray().Contains(info.AvailableType.FullyQualifiedMetadataName) &&
!info.Options.ExcludeGeneratedTypes.AsImmutableArray().Contains(info.AvailableType.FullyQualifiedMetadataName);
return isExplicitlyIncluded && !isExplicitlyExcluded;
}

// If there is no list of explicit types, still ignore types that are explicitly excluded
if (isExplicitlyExcluded)
{
return false;
}

// Otherwise, the selected types are all language support ones, and runtime support ones if selected
return LanguageSupportTypeNames.Contains(info.AvailableType.FullyQualifiedMetadataName) || info.Options.IncludeRuntimeSupportedAttributes;
return LanguageSupportTypeNames.Contains(info.FullyQualifiedTypeName) || info.Options.IncludeRuntimeSupportedAttributes;
}

/// <summary>
/// Checks whether a given <see cref="AvailableType"/> is selected for generation.
/// </summary>
/// <param name="info">The input info for the current generation.</param>
/// <returns>Whether the current <see cref="AvailableType"/> is selected for generation</returns>
private static bool IsAvailableTypeSelected((AvailableType AvailableType, GenerationOptions Options) info)
{
return IsAvailableTypeSelected((info.AvailableType.FullyQualifiedMetadataName, info.Options));
}

/// <summary>
Expand All @@ -189,9 +208,9 @@ private static GeneratedType GetGeneratedType((AvailableType AvailableType, Gene
static SyntaxFixupType GetSyntaxFixupType(AvailableType availableType, GenerationOptions options)
{
if (availableType.FullyQualifiedMetadataName is "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" &&
options.UseTypeAliasForUnmanagedCallersOnlyAttribute)
options.UseInteropServices2NamespaceForUnmanagedCallersOnlyAttribute)
{
return SyntaxFixupType.AliasUnmanagedCallersOnlyAttributeType;
return SyntaxFixupType.UseInteropServices2ForUnmanagedCallersOnlyAttribute;
}

return SyntaxFixupType.None;
Expand Down Expand Up @@ -243,19 +262,12 @@ private void EmitGeneratedType(SourceProductionContext context, GeneratedType ty
adjustedSource = MethodImplOptionsRegex.Replace(adjustedSource, "");
}

if (type.FixupType == SyntaxFixupType.AliasUnmanagedCallersOnlyAttributeType)
if (type.FixupType == SyntaxFixupType.UseInteropServices2ForUnmanagedCallersOnlyAttribute)
{
// Update the namespace and add the type alias
adjustedSource = adjustedSource.Replace(
"namespace System.Runtime.InteropServices",
"""
global using UnmanagedCallersOnlyAttribute = global::System.Runtime.InteropServices2.UnmanagedCallersOnlyAttribute;
namespace System.Runtime.InteropServices2
""");

// Adjust any remaining references
adjustedSource = adjustedSource.Replace("System.Runtime.InteropServices.", "System.Runtime.InteropServices2.");
"System.Runtime.InteropServices",
"System.Runtime.InteropServices2");
}

sourceText = SourceText.From(adjustedSource, Encoding.UTF8);
Expand Down
Loading

0 comments on commit db53c4a

Please sign in to comment.