Skip to content

Commit e36b937

Browse files
authored
Make constructors private for parts of the ComInterfaceGenerator that should only be created from their static methods (#101740)
Moves ComClassInfo to its own file and moves the Func that produces them in the ComClassGenerator pipeline to a static method on ComClassInfo. Makes the constructors for ComClassInfo, ComInterfaceContext, ComInterfaceInfo, and `ComMethoInfo private to enforce that they are only created from the static creation methods. Creates more static SpecialTypeInfo types for sbyte, uint, short, and ushort. Changes pattern matching on records to use property pattern matching rather than the deconstruction notation for records. Since the constructors are not accessible, neither are the deconstruct methods.
1 parent 9c9155d commit e36b937

File tree

12 files changed

+171
-77
lines changed

12 files changed

+171
-77
lines changed

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ namespace Microsoft.Interop
1616
[Generator]
1717
public class ComClassGenerator : IIncrementalGenerator
1818
{
19-
private sealed record ComClassInfo(string ClassName, ContainingSyntaxContext ContainingSyntaxContext, ContainingSyntax ClassSyntax, SequenceEqualImmutableArray<string> ImplementedInterfacesNames);
2019
public void Initialize(IncrementalGeneratorInitializationContext context)
2120
{
2221
var unsafeCodeIsEnabled = context.CompilationProvider.Select((comp, ct) => comp.Options is CSharpCompilationOptions { AllowUnsafe: true }); // Unsafe code enabled
@@ -27,54 +26,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2726
static (node, ct) => node is ClassDeclarationSyntax,
2827
static (context, ct) => context)
2928
.Combine(unsafeCodeIsEnabled)
30-
.Select((data, ct) =>
29+
.Select(static (data, ct) =>
3130
{
3231
var context = data.Left;
3332
var unsafeCodeIsEnabled = data.Right;
3433
var type = (INamedTypeSymbol)context.TargetSymbol;
3534
var syntax = (ClassDeclarationSyntax)context.TargetNode;
36-
if (!unsafeCodeIsEnabled)
37-
{
38-
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, syntax.Identifier.GetLocation()));
39-
}
40-
41-
if (!syntax.IsInPartialContext(out _))
42-
{
43-
return DiagnosticOr<ComClassInfo>.From(
44-
DiagnosticInfo.Create(
45-
GeneratorDiagnostics.InvalidAttributedClassMissingPartialModifier,
46-
syntax.Identifier.GetLocation(),
47-
type.ToDisplayString()));
48-
}
49-
50-
ImmutableArray<string>.Builder names = ImmutableArray.CreateBuilder<string>();
51-
foreach (INamedTypeSymbol iface in type.AllInterfaces)
52-
{
53-
AttributeData? generatedComInterfaceAttribute = iface.GetAttributes().FirstOrDefault(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute);
54-
if (generatedComInterfaceAttribute is not null)
55-
{
56-
var attributeData = GeneratedComInterfaceCompilationData.GetDataFromAttribute(generatedComInterfaceAttribute);
57-
if (attributeData.Options.HasFlag(ComInterfaceOptions.ManagedObjectWrapper))
58-
{
59-
names.Add(iface.ToDisplayString());
60-
}
61-
}
62-
}
63-
64-
if (names.Count == 0)
65-
{
66-
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.ClassDoesNotImplementAnyGeneratedComInterface,
67-
syntax.Identifier.GetLocation(),
68-
type.ToDisplayString()));
69-
}
70-
71-
72-
return DiagnosticOr<ComClassInfo>.From(
73-
new ComClassInfo(
74-
type.ToDisplayString(),
75-
new ContainingSyntaxContext(syntax),
76-
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
77-
new(names.ToImmutable())));
35+
return ComClassInfo.From(type, syntax, unsafeCodeIsEnabled);
7836
});
7937

8038
var attributedClasses = context.FilterAndReportDiagnostics(attributedClassesOrDiagnostics);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
9+
namespace Microsoft.Interop
10+
{
11+
internal sealed record ComClassInfo
12+
{
13+
public string ClassName { get; init; }
14+
public ContainingSyntaxContext ContainingSyntaxContext { get; init; }
15+
public ContainingSyntax ClassSyntax { get; init; }
16+
public SequenceEqualImmutableArray<string> ImplementedInterfacesNames { get; init; }
17+
18+
private ComClassInfo(string className, ContainingSyntaxContext containingSyntaxContext, ContainingSyntax classSyntax, SequenceEqualImmutableArray<string> implementedInterfacesNames)
19+
{
20+
ClassName = className;
21+
ContainingSyntaxContext = containingSyntaxContext;
22+
ClassSyntax = classSyntax;
23+
ImplementedInterfacesNames = implementedInterfacesNames;
24+
}
25+
26+
public static DiagnosticOr<ComClassInfo> From(INamedTypeSymbol type, ClassDeclarationSyntax syntax, bool unsafeCodeIsEnabled)
27+
{
28+
if (!unsafeCodeIsEnabled)
29+
{
30+
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, syntax.Identifier.GetLocation()));
31+
}
32+
33+
if (!syntax.IsInPartialContext(out _))
34+
{
35+
return DiagnosticOr<ComClassInfo>.From(
36+
DiagnosticInfo.Create(
37+
GeneratorDiagnostics.InvalidAttributedClassMissingPartialModifier,
38+
syntax.Identifier.GetLocation(),
39+
type.ToDisplayString()));
40+
}
41+
42+
ImmutableArray<string>.Builder names = ImmutableArray.CreateBuilder<string>();
43+
foreach (INamedTypeSymbol iface in type.AllInterfaces)
44+
{
45+
AttributeData? generatedComInterfaceAttribute = iface.GetAttributes().FirstOrDefault(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute);
46+
if (generatedComInterfaceAttribute is not null)
47+
{
48+
var attributeData = GeneratedComInterfaceCompilationData.GetDataFromAttribute(generatedComInterfaceAttribute);
49+
if (attributeData.Options.HasFlag(ComInterfaceOptions.ManagedObjectWrapper))
50+
{
51+
names.Add(iface.ToDisplayString());
52+
}
53+
}
54+
}
55+
56+
if (names.Count == 0)
57+
{
58+
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.ClassDoesNotImplementAnyGeneratedComInterface,
59+
syntax.Identifier.GetLocation(),
60+
type.ToDisplayString()));
61+
}
62+
63+
return DiagnosticOr<ComClassInfo>.From(
64+
new ComClassInfo(
65+
type.ToDisplayString(),
66+
new ContainingSyntaxContext(syntax),
67+
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
68+
new(names.ToImmutable())));
69+
}
70+
71+
public bool Equals(ComClassInfo? other)
72+
{
73+
return other is not null
74+
&& ClassName == other.ClassName
75+
&& ContainingSyntaxContext.Equals(other.ContainingSyntaxContext)
76+
&& ImplementedInterfacesNames.SequenceEqual(other.ImplementedInterfacesNames);
77+
}
78+
79+
public override int GetHashCode()
80+
{
81+
return HashCode.Combine(ClassName, ContainingSyntaxContext, ImplementedInterfacesNames);
82+
}
83+
}
84+
}

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Collections.Immutable;
67
using System.Diagnostics;
@@ -9,8 +10,19 @@
910

1011
namespace Microsoft.Interop
1112
{
12-
internal sealed record ComInterfaceContext(ComInterfaceInfo Info, ComInterfaceContext? Base, ComInterfaceOptions Options)
13+
internal sealed record ComInterfaceContext
1314
{
15+
internal ComInterfaceInfo Info { get; init; }
16+
internal ComInterfaceContext? Base { get; init; }
17+
internal ComInterfaceOptions Options { get; init; }
18+
19+
private ComInterfaceContext(ComInterfaceInfo info, ComInterfaceContext? @base, ComInterfaceOptions options)
20+
{
21+
Info = info;
22+
Base = @base;
23+
Options = options;
24+
}
25+
1426
/// <summary>
1527
/// Takes a list of ComInterfaceInfo, and creates a list of ComInterfaceContext.
1628
/// </summary>

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,40 @@ namespace Microsoft.Interop
1414
/// <summary>
1515
/// Information about a Com interface, but not its methods.
1616
/// </summary>
17-
internal sealed record ComInterfaceInfo(
18-
ManagedTypeInfo Type,
19-
string ThisInterfaceKey, // For associating interfaces to its base
20-
string? BaseInterfaceKey, // For associating interfaces to its base
21-
InterfaceDeclarationSyntax Declaration,
22-
ContainingSyntaxContext TypeDefinitionContext,
23-
ContainingSyntax ContainingSyntax,
24-
Guid InterfaceId,
25-
ComInterfaceOptions Options,
26-
Location DiagnosticLocation)
17+
internal sealed record ComInterfaceInfo
2718
{
19+
public ManagedTypeInfo Type { get; init; }
20+
public string ThisInterfaceKey { get; init; }
21+
public string? BaseInterfaceKey { get; init; }
22+
public InterfaceDeclarationSyntax Declaration { get; init; }
23+
public ContainingSyntaxContext TypeDefinitionContext { get; init; }
24+
public ContainingSyntax ContainingSyntax { get; init; }
25+
public Guid InterfaceId { get; init; }
26+
public ComInterfaceOptions Options { get; init; }
27+
public Location DiagnosticLocation { get; init; }
28+
29+
private ComInterfaceInfo(
30+
ManagedTypeInfo type,
31+
string thisInterfaceKey,
32+
string? baseInterfaceKey,
33+
InterfaceDeclarationSyntax declaration,
34+
ContainingSyntaxContext typeDefinitionContext,
35+
ContainingSyntax containingSyntax,
36+
Guid interfaceId,
37+
ComInterfaceOptions options,
38+
Location diagnosticLocation)
39+
{
40+
Type = type;
41+
ThisInterfaceKey = thisInterfaceKey;
42+
BaseInterfaceKey = baseInterfaceKey;
43+
Declaration = declaration;
44+
TypeDefinitionContext = typeDefinitionContext;
45+
ContainingSyntax = containingSyntax;
46+
InterfaceId = interfaceId;
47+
Options = options;
48+
DiagnosticLocation = diagnosticLocation;
49+
}
50+
2851
public static DiagnosticOrInterfaceInfo From(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax, StubEnvironment env, CancellationToken _)
2952
{
3053
if (env.Compilation.Options is not CSharpCompilationOptions { AllowUnsafe: true }) // Unsafe code enabled

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Immutable;
56
using System.Diagnostics;
67
using System.Linq;
@@ -14,12 +15,25 @@ namespace Microsoft.Interop
1415
/// <summary>
1516
/// Represents a method that has been determined to be a COM interface method. Only contains info immediately available from an IMethodSymbol and MethodDeclarationSyntax.
1617
/// </summary>
17-
internal sealed record ComMethodInfo(
18-
MethodDeclarationSyntax Syntax,
19-
string MethodName,
20-
SequenceEqualImmutableArray<AttributeInfo> Attributes,
21-
bool IsUserDefinedShadowingMethod)
18+
internal sealed record ComMethodInfo
2219
{
20+
public MethodDeclarationSyntax Syntax { get; init; }
21+
public string MethodName { get; init; }
22+
public SequenceEqualImmutableArray<AttributeInfo> Attributes { get; init; }
23+
public bool IsUserDefinedShadowingMethod { get; init; }
24+
25+
private ComMethodInfo(
26+
MethodDeclarationSyntax syntax,
27+
string methodName,
28+
SequenceEqualImmutableArray<AttributeInfo> attributes,
29+
bool isUserDefinedShadowingMethod)
30+
{
31+
Syntax = syntax;
32+
MethodName = methodName;
33+
Attributes = attributes;
34+
IsUserDefinedShadowingMethod = isUserDefinedShadowingMethod;
35+
}
36+
2337
/// <summary>
2438
/// Returns a list of tuples of ComMethodInfo, IMethodSymbol, and Diagnostic. If ComMethodInfo is null, Diagnostic will not be null, and vice versa.
2539
/// </summary>
@@ -95,7 +109,6 @@ internal sealed record ComMethodInfo(
95109
return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(DiagnosticInfo.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, method.Locations.FirstOrDefault(), method.ToDisplayString()));
96110
}
97111

98-
99112
// Find the matching declaration syntax
100113
MethodDeclarationSyntax? comMethodDeclaringSyntax = null;
101114
foreach (var declaringSyntaxReference in method.DeclaringSyntaxReferences)

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ public static ManagedTypeInfo CreateTypeInfoForTypeSymbol(ITypeSymbol type)
8080
public sealed record SpecialTypeInfo(string FullTypeName, string DiagnosticFormattedName, SpecialType SpecialType) : ManagedTypeInfo(FullTypeName, DiagnosticFormattedName)
8181
{
8282
public static readonly SpecialTypeInfo Byte = new("byte", "byte", SpecialType.System_Byte);
83+
public static readonly SpecialTypeInfo SByte = new("sbyte", "sbyte", SpecialType.System_SByte);
84+
public static readonly SpecialTypeInfo Int16 = new("short", "short", SpecialType.System_Int16);
85+
public static readonly SpecialTypeInfo UInt16 = new("ushort", "ushort", SpecialType.System_UInt16);
8386
public static readonly SpecialTypeInfo Int32 = new("int", "int", SpecialType.System_Int32);
87+
public static readonly SpecialTypeInfo UInt32 = new("uint", "uint", SpecialType.System_UInt32);
8488
public static readonly SpecialTypeInfo Void = new("void", "void", SpecialType.System_Void);
8589
public static readonly SpecialTypeInfo String = new("string", "string", SpecialType.System_String);
8690
public static readonly SpecialTypeInfo Boolean = new("bool", "bool", SpecialType.System_Boolean);

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/AttributedMarshallingModelGeneratorResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ private ResolvedGenerator CreateNativeCollectionMarshaller(
340340
marshallerType = marshallerType with
341341
{
342342
FullTypeName = marshallerTypeSyntax.ToString(),
343-
DiagnosticFormattedName = marshallerTypeSyntax.ToString(),
343+
DiagnosticFormattedName = marshallerTypeSyntax.ToString()
344344
};
345345
string newNativeTypeName = ReplacePlaceholderSyntaxWithUnmanagedTypeSyntax(marshallerData.NativeType.Syntax, marshalInfo, unmanagedElementType).ToFullString();
346346
ManagedTypeInfo nativeType = marshallerData.NativeType with

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BoolMarshaller.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected BoolMarshallerBase(ManagedTypeInfo nativeType, int trueValue, int fals
2828

2929
public ManagedTypeInfo AsNativeType(TypePositionInfo info)
3030
{
31-
Debug.Assert(info.ManagedType is SpecialTypeInfo(_, _, SpecialType.System_Boolean));
31+
Debug.Assert(info.ManagedType is SpecialTypeInfo { SpecialType: SpecialType.System_Boolean });
3232
return _nativeType;
3333
}
3434

@@ -118,7 +118,7 @@ public sealed class ByteBoolMarshaller : BoolMarshallerBase
118118
/// </summary>
119119
/// <param name="signed">True if the byte should be signed, otherwise false</param>
120120
public ByteBoolMarshaller(bool signed)
121-
: base(new SpecialTypeInfo(signed ? "sbyte" : "byte", signed ? "sbyte" : "byte", signed ? SpecialType.System_SByte : SpecialType.System_Byte), trueValue: 1, falseValue: 0, compareToTrue: false)
121+
: base(signed ? SpecialTypeInfo.SByte : SpecialTypeInfo.Byte, trueValue: 1, falseValue: 0, compareToTrue: false)
122122
{
123123
}
124124
}
@@ -136,7 +136,7 @@ public sealed class WinBoolMarshaller : BoolMarshallerBase
136136
/// </summary>
137137
/// <param name="signed">True if the int should be signed, otherwise false</param>
138138
public WinBoolMarshaller(bool signed)
139-
: base(new SpecialTypeInfo(signed ? "int" : "uint", signed ? "int" : "uint", signed ? SpecialType.System_Int32 : SpecialType.System_UInt32), trueValue: 1, falseValue: 0, compareToTrue: false)
139+
: base(signed ? SpecialTypeInfo.Int32 : SpecialTypeInfo.UInt32, trueValue: 1, falseValue: 0, compareToTrue: false)
140140
{
141141
}
142142
}
@@ -149,7 +149,7 @@ public sealed class VariantBoolMarshaller : BoolMarshallerBase
149149
private const short VARIANT_TRUE = -1;
150150
private const short VARIANT_FALSE = 0;
151151
public VariantBoolMarshaller()
152-
: base(new SpecialTypeInfo("short", "short", SpecialType.System_Int16), trueValue: VARIANT_TRUE, falseValue: VARIANT_FALSE, compareToTrue: true)
152+
: base(SpecialTypeInfo.Int16, trueValue: VARIANT_TRUE, falseValue: VARIANT_FALSE, compareToTrue: true)
153153
{
154154
}
155155
}

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BreakingChangeDetector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public ResolvedGenerator Create(TypePositionInfo info, StubCodeContext context)
2323
}
2424

2525
// Breaking change: [MarshalAs(UnmanagedType.Struct)] in object in unmanaged-to-managed scenarios will not respect VT_BYREF.
26-
if (info is { RefKind: RefKind.In or RefKind.RefReadOnlyParameter, MarshallingAttributeInfo: NativeMarshallingAttributeInfo(ManagedTypeInfo(_, TypeNames.ComVariantMarshaller), _) }
26+
if (info is { RefKind: RefKind.In or RefKind.RefReadOnlyParameter, MarshallingAttributeInfo: NativeMarshallingAttributeInfo(ManagedTypeInfo { DiagnosticFormattedName: TypeNames.ComVariantMarshaller }, _) }
2727
&& context.Direction == MarshalDirection.UnmanagedToManaged)
2828
{
2929
gen = ResolvedGenerator.ResolvedWithDiagnostics(

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CharMarshaller.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.Interop
1313
{
1414
public sealed class Utf16CharMarshaller : IMarshallingGenerator
1515
{
16-
private static readonly ManagedTypeInfo s_nativeType = new SpecialTypeInfo("ushort", "ushort", SpecialType.System_UInt16);
16+
private static readonly ManagedTypeInfo s_nativeType = SpecialTypeInfo.UInt16;
1717

1818
public ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, StubCodeContext context)
1919
{
@@ -35,7 +35,7 @@ public ValueBoundaryBehavior GetValueBoundaryBehavior(TypePositionInfo info, Stu
3535

3636
public ManagedTypeInfo AsNativeType(TypePositionInfo info)
3737
{
38-
Debug.Assert(info.ManagedType is SpecialTypeInfo(_, _, SpecialType.System_Char));
38+
Debug.Assert(info.ManagedType is SpecialTypeInfo {SpecialType: SpecialType.System_Char });
3939
return s_nativeType;
4040
}
4141

0 commit comments

Comments
 (0)