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
90 changes: 77 additions & 13 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public class Generator : IDisposable
private static readonly AttributeSyntax PreserveSigAttribute = Attribute(IdentifierName("PreserveSig"));
private static readonly AttributeSyntax ObsoleteAttribute = Attribute(IdentifierName("Obsolete")).WithArgumentList(null);
private static readonly AttributeSyntax SupportedOSPlatformAttribute = Attribute(IdentifierName("SupportedOSPlatform"));
private static readonly AttributeSyntax UnscopedRefAttribute = Attribute(ParseName("UnscopedRef")).WithArgumentList(null);
private static readonly AttributeListSyntax DefaultDllImportSearchPathsAttributeList = AttributeList()
.WithCloseBracketToken(TokenWithLineFeed(SyntaxKind.CloseBracketToken))
.AddAttributes(Attribute(IdentifierName("DefaultDllImportSearchPaths")).AddArgumentListArguments(AttributeArgument(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(DllImportSearchPath)), IdentifierName(nameof(DllImportSearchPath.System32))))));
Expand Down Expand Up @@ -337,6 +338,7 @@ public class Generator : IDisposable
private readonly bool canCallCreateSpan;
private readonly bool canUseUnsafeAsRef;
private readonly bool canUseUnsafeNullRef;
private readonly bool unscopedRefAttributePredefined;
private readonly bool getDelegateForFunctionPointerGenericExists;
private readonly bool generateSupportedOSPlatformAttributes;
private readonly bool generateSupportedOSPlatformAttributesOnInterfaces; // only supported on net6.0 (https://github.com/dotnet/runtime/pull/48838)
Expand Down Expand Up @@ -384,6 +386,7 @@ public Generator(string metadataLibraryPath, Docs? docs, GeneratorOptions option
this.canCallCreateSpan = this.compilation?.GetTypeByMetadataName(typeof(MemoryMarshal).FullName)?.GetMembers("CreateSpan").Any() is true;
this.canUseUnsafeAsRef = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("AsRef").Any() is true;
this.canUseUnsafeNullRef = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("NullRef").Any() is true;
this.unscopedRefAttributePredefined = this.compilation?.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.UnscopedRefAttribute") is not null;
this.getDelegateForFunctionPointerGenericExists = this.compilation?.GetTypeByMetadataName(typeof(Marshal).FullName)?.GetMembers(nameof(Marshal.GetDelegateForFunctionPointer)).Any(m => m is IMethodSymbol { IsGenericMethod: true }) is true;
this.generateDefaultDllImportSearchPathsAttribute = this.compilation?.GetTypeByMetadataName(typeof(DefaultDllImportSearchPathsAttribute).FullName) is object;
if (this.compilation?.GetTypeByMetadataName("System.Runtime.Versioning.SupportedOSPlatformAttribute") is { } attribute
Expand Down Expand Up @@ -1086,7 +1089,7 @@ public IReadOnlyDictionary<string, CompilationUnitSyntax> GetCompilationUnits(Ca

// .g.cs because the resulting files are not user-created.
const string FilenamePattern = "{0}.g.cs";
var results = new Dictionary<string, NamespaceDeclarationSyntax>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, CompilationUnitSyntax> results = new(StringComparer.OrdinalIgnoreCase);

IEnumerable<MemberDeclarationSyntax> GroupMembersByNamespace(IEnumerable<MemberDeclarationSyntax> members)
{
Expand All @@ -1100,12 +1103,23 @@ nsContents.Key is object

if (this.options.EmitSingleFile)
{
CompilationUnitSyntax file = CompilationUnit()
.AddMembers(starterNamespace.AddMembers(GroupMembersByNamespace(this.NamespaceMembers).ToArray()))
.AddMembers(this.committedCode.GeneratedTopLevelTypes.ToArray());
results.Add(
string.Format(CultureInfo.InvariantCulture, FilenamePattern, "NativeMethods"),
starterNamespace.AddMembers(GroupMembersByNamespace(this.NamespaceMembers).ToArray()));
file);
}
else
{
foreach (MemberDeclarationSyntax topLevelType in this.committedCode.GeneratedTopLevelTypes)
{
string typeName = topLevelType.DescendantNodesAndSelf().OfType<BaseTypeDeclarationSyntax>().First().Identifier.ValueText;
results.Add(
string.Format(CultureInfo.InvariantCulture, FilenamePattern, typeName),
CompilationUnit().AddMembers(topLevelType));
}

IEnumerable<IGrouping<string?, MemberDeclarationSyntax>>? membersByFile = this.NamespaceMembers.GroupBy(
member => member.HasAnnotations(SimpleFileNameAnnotation)
? member.GetAnnotations(SimpleFileNameAnnotation).Single().Data
Expand All @@ -1124,9 +1138,11 @@ nsContents.Key is object
{
try
{
CompilationUnitSyntax file = CompilationUnit()
.AddMembers(starterNamespace.AddMembers(GroupMembersByNamespace(fileSimpleName).ToArray()));
results.Add(
string.Format(CultureInfo.InvariantCulture, FilenamePattern, fileSimpleName.Key),
starterNamespace.AddMembers(GroupMembersByNamespace(fileSimpleName).ToArray()));
file);
}
catch (ArgumentException ex)
{
Expand All @@ -1139,6 +1155,7 @@ nsContents.Key is object
{
UsingDirective(AliasQualifiedName(IdentifierName(Token(SyntaxKind.GlobalKeyword)), IdentifierName(nameof(System)))),
UsingDirective(AliasQualifiedName(IdentifierName(Token(SyntaxKind.GlobalKeyword)), IdentifierName(nameof(System) + "." + nameof(System.Diagnostics)))),
UsingDirective(AliasQualifiedName(IdentifierName(Token(SyntaxKind.GlobalKeyword)), IdentifierName(nameof(System) + "." + nameof(System.Diagnostics) + "." + nameof(System.Diagnostics.CodeAnalysis)))),
UsingDirective(ParseName(GlobalNamespacePrefix + SystemRuntimeCompilerServices)),
UsingDirective(ParseName(GlobalNamespacePrefix + SystemRuntimeInteropServices)),
};
Expand All @@ -1153,9 +1170,8 @@ nsContents.Key is object
var normalizedResults = new Dictionary<string, CompilationUnitSyntax>(StringComparer.OrdinalIgnoreCase);
results.AsParallel().WithCancellation(cancellationToken).ForAll(kv =>
{
CompilationUnitSyntax? compilationUnit = ((CompilationUnitSyntax)CompilationUnit()
.AddMembers(
kv.Value.AddUsings(usingDirectives.ToArray()))
CompilationUnitSyntax? compilationUnit = ((CompilationUnitSyntax)kv.Value
.AddUsings(usingDirectives.ToArray())
.Accept(new WhitespaceRewriter())!)
.WithLeadingTrivia(FileHeader);

Expand Down Expand Up @@ -4997,6 +5013,7 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter
return (ranklessArray, default(SyntaxList<MemberDeclarationSyntax>), marshalAs);
}

SyntaxList<MemberDeclarationSyntax> additionalMembers = default;
int length = int.Parse(((LiteralExpressionSyntax)arrayType.RankSpecifiers[0].Sizes[0]).Token.ValueText, CultureInfo.InvariantCulture);
TypeSyntax elementType = arrayType.ElementType;

Expand Down Expand Up @@ -5046,8 +5063,8 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter
if (this.canCallCreateSpan)
{
// ...
// internal ref TheStruct this[int index] => ref AsSpan()[index];
// internal Span<TheStruct> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 4);
// [UnscopedRef] internal ref TheStruct this[int index] => ref AsSpan()[index];
// [UnscopedRef] internal Span<TheStruct> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 4);
fixedLengthStruct = fixedLengthStruct
.AddMembers(
IndexerDeclaration(RefType(elementType).WithTrailingTrivia(TriviaList(Space)))
Expand All @@ -5057,6 +5074,7 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter
ElementAccessExpression(InvocationExpression(IdentifierName("AsSpan")))
.AddArgumentListArguments(Argument(IdentifierName("index"))))))
.WithSemicolonToken(SemicolonWithLineFeed)
.AddAttributeLists(AttributeList().AddAttributes(UnscopedRefAttribute))
.WithLeadingTrivia(InlineArrayUnsafeIndexerComment),
MethodDeclaration(MakeSpanOfT(elementType).WithTrailingTrivia(TriviaList(Space)), Identifier("AsSpan"))
.AddModifiers(TokenWithSpace(this.Visibility))
Expand All @@ -5066,7 +5084,9 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter
Argument(nameColon: null, TokenWithSpace(SyntaxKind.RefKeyword), firstElementFieldName),
Argument(lengthLiteralSyntax))))))
.WithSemicolonToken(SemicolonWithLineFeed)
.AddAttributeLists(AttributeList().AddAttributes(UnscopedRefAttribute))
.WithLeadingTrivia(InlineArrayUnsafeAsSpanComment));
this.DeclareUnscopedRefAttributeIfNecessary();
}

#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
Expand Down Expand Up @@ -5486,7 +5506,41 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter
.WithBody(body);
this.volatileCode.AddInlineArrayIndexerExtension(getOrSetAtMethod);

return (fixedLengthStructName, List<MemberDeclarationSyntax>().Add(fixedLengthStruct), null);
additionalMembers = additionalMembers.Add(fixedLengthStruct);
return (fixedLengthStructName, additionalMembers, null);
}

private void DeclareUnscopedRefAttributeIfNecessary()
{
if (this.unscopedRefAttributePredefined)
{
return;
}

const string name = "UnscopedRefAttribute";
this.volatileCode.GenerateSpecialType(name, delegate
{
ExpressionSyntax[] uses = new[]
{
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(AttributeTargets)), IdentifierName(nameof(AttributeTargets.Method))),
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(AttributeTargets)), IdentifierName(nameof(AttributeTargets.Property))),
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(AttributeTargets)), IdentifierName(nameof(AttributeTargets.Parameter))),
};
AttributeListSyntax usageAttr = AttributeList().AddAttributes(
Attribute(IdentifierName(nameof(AttributeUsageAttribute))).AddArgumentListArguments(
AttributeArgument(
uses.Aggregate((left, right) => BinaryExpression(SyntaxKind.BitwiseOrExpression, left, right))),
AttributeArgument(LiteralExpression(SyntaxKind.FalseLiteralExpression)).WithNameEquals(NameEquals(IdentifierName("AllowMultiple"))),
AttributeArgument(LiteralExpression(SyntaxKind.FalseLiteralExpression)).WithNameEquals(NameEquals(IdentifierName("Inherited")))));
ClassDeclarationSyntax attrDecl = ClassDeclaration(Identifier("UnscopedRefAttribute"))
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(IdentifierName("Attribute")))))
.AddModifiers(Token(SyntaxKind.InternalKeyword), TokenWithSpace(SyntaxKind.SealedKeyword))
.AddAttributeLists(usageAttr);
NamespaceDeclarationSyntax nsDeclaration = NamespaceDeclaration(ParseName("System.Diagnostics.CodeAnalysis"))
.AddMembers(attrDecl);

this.volatileCode.AddSpecialType(name, nsDeclaration, topLevel: true);
});
}

private bool IsTypeDefStruct(TypeHandleInfo? typeHandleInfo)
Expand Down Expand Up @@ -5844,7 +5898,7 @@ private class GeneratedCode

private readonly List<ClassDeclarationSyntax> safeHandleTypes = new();

private readonly Dictionary<string, MemberDeclarationSyntax> specialTypes = new(StringComparer.Ordinal);
private readonly Dictionary<string, (MemberDeclarationSyntax Type, bool TopLevel)> specialTypes = new(StringComparer.Ordinal);

/// <summary>
/// The set of types that are or have been generated so we don't stack overflow for self-referencing types.
Expand Down Expand Up @@ -5881,7 +5935,11 @@ internal GeneratedCode(GeneratedCode parent)
internal bool IsEmpty => this.modulesAndMembers.Count == 0 && this.types.Count == 0 && this.fieldsToSyntax.Count == 0 && this.safeHandleTypes.Count == 0 && this.specialTypes.Count == 0
&& this.inlineArrayIndexerExtensionsMembers.Count == 0 && this.comInterfaceFriendlyExtensionsMembers.Count == 0;

internal IEnumerable<MemberDeclarationSyntax> GeneratedTypes => this.GetTypesWithInjectedFields().Concat(this.specialTypes.Values).Concat(this.safeHandleTypes);
internal IEnumerable<MemberDeclarationSyntax> GeneratedTypes => this.GetTypesWithInjectedFields()
.Concat(this.specialTypes.Values.Where(st => !st.TopLevel).Select(st => st.Type))
.Concat(this.safeHandleTypes);

internal IEnumerable<MemberDeclarationSyntax> GeneratedTopLevelTypes => this.specialTypes.Values.Where(st => st.TopLevel).Select(st => st.Type);

internal IEnumerable<MethodDeclarationSyntax> ComInterfaceExtensions => this.comInterfaceFriendlyExtensionsMembers;

Expand Down Expand Up @@ -5962,10 +6020,16 @@ internal void AddComInterfaceExtension(IEnumerable<MethodDeclarationSyntax> exte
this.comInterfaceFriendlyExtensionsMembers.AddRange(extension);
}

internal void AddSpecialType(string specialName, MemberDeclarationSyntax specialDeclaration)
/// <summary>
/// Adds a declaration to the generated code.
/// </summary>
/// <param name="specialName">The same constant provided to <see cref="GenerateSpecialType(string, Action)"/>. This serves to avoid repeat declarations.</param>
/// <param name="specialDeclaration">The declaration.</param>
/// <param name="topLevel"><see langword="true" /> if this declaration should <em>not</em> be nested within the top-level namespace for generated code.</param>
internal void AddSpecialType(string specialName, MemberDeclarationSyntax specialDeclaration, bool topLevel = false)
{
this.ThrowIfNotGenerating();
this.specialTypes.Add(specialName, specialDeclaration);
this.specialTypes.Add(specialName, (specialDeclaration, topLevel));
}

internal void AddInteropType(TypeDefinitionHandle typeDefinitionHandle, bool hasUnmanagedName, MemberDeclarationSyntax typeDeclaration)
Expand Down
3 changes: 3 additions & 0 deletions test/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ dotnet_diagnostic.SA1401.severity = silent

# SA1133: Do not combine attributes
dotnet_diagnostic.SA1133.severity = silent

# SA1611: Element parameters should be documented
dotnet_diagnostic.SA1611.severity = suggestion
Loading